commit 0457e3a587b26c319f06b7c5f955fd64912b1afc Author: Geoffrey Thomas Date: Sun Jul 20 18:28:02 2014 -0700 Initial import of linmodem-0.2.5.tgz diff --git a/CHANGES b/CHANGES new file mode 100644 index 0000000..1719c33 --- /dev/null +++ b/CHANGES @@ -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 diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..60549be --- /dev/null +++ b/COPYING @@ -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. + + + Copyright (C) 19yy + + 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. + + , 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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8de6622 --- /dev/null +++ b/Makefile @@ -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 diff --git a/README b/README new file mode 100644 index 0000000..606d7ad --- /dev/null +++ b/README @@ -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 diff --git a/README.arch b/README.arch new file mode 100644 index 0000000..1ad7086 --- /dev/null +++ b/README.arch @@ -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. + diff --git a/README.x11 b/README.x11 new file mode 100644 index 0000000..b94e537 --- /dev/null +++ b/README.x11 @@ -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 diff --git a/atparser.c b/atparser.c new file mode 100644 index 0000000..d4e215f --- /dev/null +++ b/atparser.c @@ -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 +#include +#include +#include +#include + +#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); + } +} diff --git a/display.c b/display.c new file mode 100644 index 0000000..c1fcbbc --- /dev/null +++ b/display.c @@ -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 +#include +#include +#include +#include +#include +#include + +#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 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= 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 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> (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) 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>=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 (jre=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>=1; + l<<=1; + } while (k); + + for(i=0,p=x;ire*=norm; + p->im*=norm; + } + + for(i=0,p=x;i> (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>= shift; + } +} + +static inline int dsp_max_bits(s16 *tab, int n) +{ + int i, max, v, b; + max = 0; + for(i=0;i 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); diff --git a/dtmf.c b/dtmf.c new file mode 100644 index 0000000..c5cd6de --- /dev/null +++ b/dtmf.c @@ -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> 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;icos_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 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;jbuf[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; + } + } +} diff --git a/dtmf.h b/dtmf.h new file mode 100644 index 0000000..5f7c5c2 --- /dev/null +++ b/dtmf.h @@ -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); diff --git a/fsk.c b/fsk.c new file mode 100644 index 0000000..f75a326 --- /dev/null +++ b/fsk.c @@ -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;ibaud_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;ifilter_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;ifilter_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); +} diff --git a/fsk.h b/fsk.h new file mode 100644 index 0000000..db2edd4 --- /dev/null +++ b/fsk.h @@ -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 diff --git a/lm.c b/lm.c new file mode 100644 index 0000000..9b8975a --- /dev/null +++ b/lm.c @@ -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 +#include +#include +#include +#include +#include + +#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( "<<>>\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; +} + + + + + diff --git a/lm.h b/lm.h new file mode 100644 index 0000000..8ad8be4 --- /dev/null +++ b/lm.h @@ -0,0 +1,213 @@ +#include +#include +#include +#include +#include + +#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" diff --git a/lmreal.c b/lmreal.c new file mode 100644 index 0000000..ed4f5ec --- /dev/null +++ b/lmreal.c @@ -0,0 +1,95 @@ +/* real modem: suitable for ltmodem */ + +/* Copyright 1999 Pavel Machek , distribute under GPL v2 */ + +#include "lm.h" +#include + +#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" ); +} diff --git a/lmsim.c b/lmsim.c new file mode 100644 index 0000000..a120aa1 --- /dev/null +++ b/lmsim.c @@ -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;itx_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;jbuf[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;ifout2 * 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); + } +} diff --git a/lmsoundcard.c b/lmsoundcard.c new file mode 100644 index 0000000..8c24caf --- /dev/null +++ b/lmsoundcard.c @@ -0,0 +1,196 @@ +/* sample interface code to use a linux soundcard */ +#include +#include +#include +#include + +#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;itx_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, +}; + diff --git a/lmstates.h b/lmstates.h new file mode 100644 index 0000000..bc6fb8a --- /dev/null +++ b/lmstates.h @@ -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 diff --git a/serial.c b/serial.c new file mode 100644 index 0000000..e4c8dcb --- /dev/null +++ b/serial.c @@ -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;jserial_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++; + } +} + diff --git a/v21.c b/v21.c new file mode 100644 index 0000000..fa3599a --- /dev/null +++ b/v21.c @@ -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; +} diff --git a/v21.h b/v21.h new file mode 100644 index 0000000..46c10f9 --- /dev/null +++ b/v21.h @@ -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); + diff --git a/v22.c b/v22.c new file mode 100644 index 0000000..c73b8bd --- /dev/null +++ b/v22.c @@ -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;ibaud_phase; + si = sq = 0; + for(j=0;jtx_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); +} diff --git a/v22.h b/v22.h new file mode 100644 index 0000000..e760fa8 --- /dev/null +++ b/v22.h @@ -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); diff --git a/v23.c b/v23.c new file mode 100644 index 0000000..a3efb30 --- /dev/null +++ b/v23.c @@ -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 +#include + +#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; +} diff --git a/v23.h b/v23.h new file mode 100644 index 0000000..8859e9f --- /dev/null +++ b/v23.h @@ -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); diff --git a/v34.c b/v34.c new file mode 100644 index 0000000..f63ea12 --- /dev/null +++ b/v34.c @@ -0,0 +1,2311 @@ +/* + * Implementation of the V34 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 V34 specification and by using basic signal processing + * knowledge. + */ + +#include "lm.h" +#include "v34priv.h" + +#define DEBUG + +void print_bits(int val, int n) +{ + int i; + + for(i=n-1;i>=0;i--) { + putchar('0' + ((val >> i) & 1)); + } +} + +void print_bit_vector(char *str, u8 *tab, int n) +{ + int i; + printf("%s[%d]= ", str, n); + for(i=n-1;i>=0;i--) putchar(tab[i] + '0'); + printf("\n"); +} + + +static void agc_init(V34DSPState *s); +static void baseband_decode(V34DSPState *s, int si, int sq); +static void put_sym(V34DSPState *s, int si, int sq); + +/* divide the bit sequence by poly */ +#define SCRAMBLER_DEG 23 +#define V34_GPC (1 | (1 << (23-18))) +#define V34_GPA (1 | (1 << (23-5))) + +int scramble_bit(V34DSPState *s, int b, int poly) +{ + int b1, reg; + + reg = s->scrambler_reg; + + b1 = (reg >> (SCRAMBLER_DEG-1)) ^ b; + reg = (reg << 1) & ((1 << SCRAMBLER_DEG) - 1); + if (b1) + reg ^= poly; + + s->scrambler_reg = reg; + + return b1; +} + +int unscramble_bit(V34DSPState *s, int b, int poly) +{ + int b1, reg; + + reg = s->scrambler_reg; + + b1 = (reg >> (SCRAMBLER_DEG-1)) ^ b; + reg = (reg << 1) & ((1 << SCRAMBLER_DEG) - 1); + if (b) + reg ^= poly; + + s->scrambler_reg = reg; + + return b1; +} + +/* build the constellation (no need to store it completely, we are lazy!) */ + +static int constellation_cmp(const void *a_ptr, const void *b_ptr) +{ + int x1,y1,x2,y2,d; + x1= ((s8 *)a_ptr)[0]; + y1= ((s8 *)a_ptr)[1]; + x2= ((s8 *)b_ptr)[0]; + y2= ((s8 *)b_ptr)[1]; + + d = (x1 * x1 + y1 * y1) - (x2 * x2 + y2 * y2) ; + if (d != 0) + return d; + else + return y2 - y1; +} + +#define rotate_clockwise(x, y, x1, y1, z)\ +{\ + switch(z) {\ + case 0:\ + x = x1;\ + y = y1;\ + break;\ + case 1:\ + x = -y1;\ + y = x1;\ + break;\ + case 2:\ + x = -x1;\ + y = -y1;\ + break;\ + default:\ + x = y1;\ + y = -x1;\ + break;\ + }\ +} + +static void build_constellation(V34DSPState *s) +{ + int x,y,i,j,k; + + k = 0; + for(y=C_MIN; y<= C_MAX; y++) { + for(x=C_MIN; x<= C_MAX; x++) { + s->constellation[k][0] = 4*x+1; + s->constellation[k][1] = 4*y+1; + k++; + } + } + + /* now sort the constellation */ + qsort(s->constellation, C_MAX_SIZE, 2, constellation_cmp); + +#if 0 + for(i=0;iconstellation[i][0], s->constellation[i][1]); +#endif + + /* build the table for the decoder (not the best table, the corners + are not ok) */ + + memset(s->constellation_to_code, 0, sizeof(s->constellation_to_code)); + for(j=0;j<4;j++) { + for(i=0;iconstellation[i][0]; + y1 = s->constellation[i][1]; + + rotate_clockwise(x, y, x1, y1, j); + + x = (x + C_RADIUS) >> 1; + y = (y + C_RADIUS) >> 1; + + s->constellation_to_code[x][y] = i | (j << 14); + } + } +} + +/* index to ring utilities */ + +static inline int g2(V34DSPState *st, int p, int m) +{ + if (p >= 0 && p <= 2*(m-1)) + return m - abs(p-(m-1)); + else { + return 0; + } +} + +static inline int g4(V34DSPState *st, int p, int m) +{ + int s,i; + + s = 0; + if (p >= 0 && p <= 4*(m-1)) { + for(i=0;i<=p;i++) s += st->g2_tab[i] * st->g2_tab[p-i]; + } + return s; +} + +static inline int g8(V34DSPState *st, int p, int m) +{ + int s,i; + + s = 0; + if (p >= 0 && p <= 8*(m-1)) { + for(i=0;i<=p;i++) s += st->g4_tab[i] * st->g4_tab[p-i]; + } + return s; +} + +static void index_to_rings(V34DSPState *s, int ring[4][2], int r0) +{ + int a,b,c,d,e,f,g,h,r1,r2,r3,r4,r5,tmp,m; + + m = s->M; + + a = -1; + r1 = 0; + for(;;) { + tmp = r0 - s->z8_tab[a+1]; + if (tmp < 0) break; + r1 = tmp; + a++; + } + + b = 0; + for(;;) { + tmp = r1 - s->g4_tab[b] * s->g4_tab[a-b]; + if (tmp < 0) break; + r1 = tmp; + b++; + } + + tmp = s->g4_tab[b]; + r2 = r1 % tmp; + r3 = (r1 - r2) / tmp; + + c = 0; + r4 = r2; + for(;;) { + tmp = r4 - s->g2_tab[c] * s->g2_tab[b-c]; + if (tmp < 0) break; + r4 = tmp; + c++; + } + + d = 0; + r5 = r3; + for(;;) { + tmp = r5 - s->g2_tab[d] * s->g2_tab[a-b-d]; + if (tmp < 0) break; + r5 = tmp; + d++; + } + + tmp = s->g2_tab[c]; + e = r4 % tmp; + f = (r4 - e) / tmp; + + tmp = s->g2_tab[d]; + g = r5 % tmp; + h = (r5 - g) / tmp; + + if (c < m) { + ring[0][0] = e; + ring[0][1] = c - ring[0][0]; + } else { + ring[0][1] = m - 1 - e; + ring[0][0] = c - ring[0][1]; + } + + if ((b-c) < m) { + ring[1][0] = f; + ring[1][1] = b - c - ring[1][0]; + } else { + ring[1][1] = m - 1 - f; + ring[1][0] = b - c - ring[1][1]; + } + + if (d < m) { + ring[2][0] = g; + ring[2][1] = d - ring[2][0]; + } else { + ring[2][1] = m - 1 - g; + ring[2][0] = d - ring[2][1]; + } + + if ((a-b-d) < m) { + ring[3][0] = h; + ring[3][1] = a - b - d - ring[3][0]; + } else { + ring[3][1] = m - 1 - h; + ring[3][0] = a - b - d - ring[3][1]; + } +} + +/* return the K bit index corresponding to the rings */ +static int rings_to_index(V34DSPState *s, int ring[4][2]) +{ + int a,b,c,d,e,f,g,h,r0,r1,r2,r3,r4,r5,m,i; + + m = s->M; + + /* find back the parameters */ + c = ring[0][0] + ring[0][1]; + if (c < m) e = ring[0][0]; else e = m - 1 - ring[0][1]; + + b = ring[1][0] + ring[1][1]; + if (b < m) f = ring[1][0]; else f = m - 1 - ring[1][1]; + b += c; + + d = ring[2][0] + ring[2][1]; + if (d < m) g = ring[2][0]; else g = m - 1 - ring[2][1]; + + a = ring[3][0] + ring[3][1]; + if (a < m) h = ring[3][0]; else h = m - 1 - ring[3][1]; + a += b + d; + + r5 = h * s->g2_tab[d] + g; + r4 = f * s->g2_tab[c] + e; + + r3 = r5; + for(i=0;ig2_tab[i] * s->g2_tab[a-b-i]; + + r2 = r4; + for(i=0;ig2_tab[i] * s->g2_tab[b-i]; + + r1 = r3 * s->g4_tab[b] + r2; + + for(i=0;ig4_tab[i] * s->g4_tab[a-i]; + + r0 = r1 + s->z8_tab[a]; + + return r0; +} + +/* initialize the g2, g4, g8 & z8 tables */ +static void build_rings(V34DSPState *s) +{ + int n,i,m; + m = s->M; + n = 8*(m - 1) + 1; + for(i=0;ig2_tab[i] = g2(s,i,m); + for(i=0;ig4_tab[i] = g4(s,i,m); + for(i=0;ig8_tab[i] = g8(s,i,m); + + s->z8_tab[0] = 0; + for(i=1;iz8_tab[i] = s->z8_tab[i-1] + s->g8_tab[i-1]; + } + +#if 0 + { + int i; + for(i=0;i<=(1 << s->K);i++) { + int m[4][2],j, r1,r0; + r0 = random() % (1 << s->K); + index_to_rings(s, m, r0); + r1 = rings_to_index(s, m); + printf("%d:", r0); + for(j=0;j<4;j++) printf(" %d %d",m[j][0], m[j][1]); + printf("\n"); + if (r0 != r1) { + printf("error r0=%d r1=%d\n" , r0, r1); + exit(1); + } + } + } +#endif + +} + +/* parameters for each symbol rate */ +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 }, +}; + +static s16 *rc_filter[6] = { + v34_rc_10_filter, + v34_rc_35_filter, + v34_rc_20_filter, + v34_rc_8_filter, + v34_rc_5_filter, + v34_rc_7_filter, +}; + +static void build_tx_filter(V34DSPState *s) +{ + /* sampled at every symbol */ + + s->tx_filter = rc_filter[s->S]; + s->baud_incr = s->symbol_rate * (float)0x10000 / (float)V34_SAMPLE_RATE; + s->baud_phase = 4; + + s->carrier_phase = 0; + s->carrier_incr = s->carrier_freq * (float)0x10000 / (float)V34_SAMPLE_RATE; + /* init TX fifo */ + s->tx_filter_wsize = RC_FILTER_SIZE; + s->tx_buf_ptr = 0; + s->tx_outbuf_ptr = s->tx_filter_wsize; + s->tx_buf_size = 0; +} + +float hilbert[156] = +{ +/* alpha=0.000000 beta=0.000000 */ + 0.0000000000e+00, + 1.0438059849e-03, + 0.0000000000e+00, + 1.1113855722e-03, + 0.0000000000e+00, + 1.2232369871e-03, + 0.0000000000e+00, + 1.3825587134e-03, + 0.0000000000e+00, + 1.5926453386e-03, + 0.0000000000e+00, + 1.8569074639e-03, + 0.0000000000e+00, + 2.1788971424e-03, + 0.0000000000e+00, + 2.5623400268e-03, + 0.0000000000e+00, + 3.0111756977e-03, + 0.0000000000e+00, + 3.5296080377e-03, + 0.0000000000e+00, + 4.1221680212e-03, + 0.0000000000e+00, + 4.7937919718e-03, + 0.0000000000e+00, + 5.5499192449e-03, + 0.0000000000e+00, + 6.3966145267e-03, + 0.0000000000e+00, + 7.3407216217e-03, + 0.0000000000e+00, + 8.3900579330e-03, + 0.0000000000e+00, + 9.5536620984e-03, + 0.0000000000e+00, + 1.0842111879e-02, + 0.0000000000e+00, + 1.2267936058e-02, + 0.0000000000e+00, + 1.3846153846e-02, + 0.0000000000e+00, + 1.5594989773e-02, + 0.0000000000e+00, + 1.7536833977e-02, + 0.0000000000e+00, + 1.9699551684e-02, + 0.0000000000e+00, + 2.2118299263e-02, + 0.0000000000e+00, + 2.4838091053e-02, + 0.0000000000e+00, + 2.7917505894e-02, + 0.0000000000e+00, + 3.1434171201e-02, + 0.0000000000e+00, + 3.5493106154e-02, + 0.0000000000e+00, + 4.0239829657e-02, + 0.0000000000e+00, + 4.5881743462e-02, + 0.0000000000e+00, + 5.2724604849e-02, + 0.0000000000e+00, + 6.1238171887e-02, + 0.0000000000e+00, + 7.2182437365e-02, + 0.0000000000e+00, + 8.6871563629e-02, + 0.0000000000e+00, + 1.0778971907e-01, + 0.0000000000e+00, + 1.4026261876e-01, + 0.0000000000e+00, + 1.9814073999e-01, + 0.0000000000e+00, + 3.3221536070e-01, + 0.0000000000e+00, + 9.9962693916e-01, + 0.0000000000e+00, + -9.9962693916e-01, + -0.0000000000e+00, + -3.3221536070e-01, + -0.0000000000e+00, + -1.9814073999e-01, + -0.0000000000e+00, + -1.4026261876e-01, + -0.0000000000e+00, + -1.0778971907e-01, + -0.0000000000e+00, + -8.6871563629e-02, + -0.0000000000e+00, + -7.2182437365e-02, + -0.0000000000e+00, + -6.1238171887e-02, + -0.0000000000e+00, + -5.2724604849e-02, + -0.0000000000e+00, + -4.5881743462e-02, + -0.0000000000e+00, + -4.0239829657e-02, + -0.0000000000e+00, + -3.5493106154e-02, + -0.0000000000e+00, + -3.1434171201e-02, + -0.0000000000e+00, + -2.7917505894e-02, + -0.0000000000e+00, + -2.4838091053e-02, + -0.0000000000e+00, + -2.2118299263e-02, + -0.0000000000e+00, + -1.9699551684e-02, + -0.0000000000e+00, + -1.7536833977e-02, + -0.0000000000e+00, + -1.5594989773e-02, + -0.0000000000e+00, + -1.3846153846e-02, + -0.0000000000e+00, + -1.2267936058e-02, + -0.0000000000e+00, + -1.0842111879e-02, + -0.0000000000e+00, + -9.5536620984e-03, + -0.0000000000e+00, + -8.3900579330e-03, + -0.0000000000e+00, + -7.3407216217e-03, + -0.0000000000e+00, + -6.3966145267e-03, + -0.0000000000e+00, + -5.5499192449e-03, + -0.0000000000e+00, + -4.7937919718e-03, + -0.0000000000e+00, + -4.1221680212e-03, + -0.0000000000e+00, + -3.5296080377e-03, + -0.0000000000e+00, + -3.0111756977e-03, + -0.0000000000e+00, + -2.5623400268e-03, + -0.0000000000e+00, + -2.1788971424e-03, + -0.0000000000e+00, + -1.8569074639e-03, + -0.0000000000e+00, + -1.5926453386e-03, + -0.0000000000e+00, + -1.3825587134e-03, + -0.0000000000e+00, + -1.2232369871e-03, + -0.0000000000e+00, + -1.1113855722e-03, + -0.0000000000e+00, + -1.0438059849e-03, +}; + + +s16 *v34_rx_filters[12] = { + v34_rx_filter_2400_1600, + v34_rx_filter_2400_1800, + v34_rx_filter_2743_1646, + v34_rx_filter_2743_1829, + v34_rx_filter_2800_1680, + v34_rx_filter_2800_1867, + v34_rx_filter_3000_1800, + v34_rx_filter_3000_2000, + v34_rx_filter_3200_1829, + v34_rx_filter_3200_1920, + v34_rx_filter_3429_1959, + v34_rx_filter_3429_1959, +}; + +static void build_rx_filter(V34DSPState *s) +{ + float a, f_low, f_high; + int i; + + + s->rx_filter = v34_rx_filters[s->S * 2 + s->use_high_carrier]; + + /* XXX: temporary hack to synchronize */ + if (s->S == V34_S3429) + s->baud_phase = 1; + else + s->baud_phase = 2; + s->baud_num = (s->baud_num * 3); + + s->carrier_incr = s->carrier_freq * (float)0x10000 / s->symbol_rate; + s->carrier_phase = 0; + s->rx_buf1_ptr = 0; + s->rx_filter_wsize = (s->baud_denom * RC_FILTER_SIZE) / s->baud_num; + printf("cincr=%d baudincr=%d\n", s->carrier_incr, s->baud_incr); + + s->baud_phase = s->baud_phase << 16; + s->baud_num = s->baud_num << 16; + s->baud_denom = s->baud_denom << 16; + + /* equalizer : init to identity */ + s->eq_filter[EQ_SIZE/2][0] = 0x4000 << 16; + /* XXX: hilbert normalization ? */ + for(i=0;ieq_filter[i][1] = (int)(hilbert[i] * 0.61475 * (0x4000 << 16)); + + /* adaptation shift : big at the beginning, should be small after. */ + s->eq_shift = 0; + + /* synchronization : Nyquist filters at the upper & lower frequencies */ + a = 0.99; + f_low = 2 * M_PI * (s->carrier_freq - s->symbol_rate / 2.0) / + (3.0 * s->symbol_rate); + f_high = 2 * M_PI * (s->carrier_freq + s->symbol_rate / 2.0) / + (3.0 * s->symbol_rate); + printf("%f %f\n", f_low, f_high); + + s->sync_low_coef[0] = (int)(2 * a * cos(f_low) * 0x4000); + s->sync_low_coef[1] = (int)( - a * a * 0x4000); + + s->sync_high_coef[0] = (int)(2 * a * cos(f_high) * 0x4000); + s->sync_high_coef[1] = (int)(- a * a * 0x4000); + + /* precomputed constants to compute the cross correlation */ + s->sync_A = (int)( - a * a * sin(f_high - f_low) * 0x4000); + s->sync_B = (int)( a * sin(f_high) * 0x4000); + s->sync_C = (int)( - a * sin(f_low) * 0x4000); +} + +int V34_init_low(V34DSPState *s, V34State *p, int transmit) +{ + int S,d,e; + + /* copy the params */ + s->calling = p->calling; + s->S = p->S; + s->expanded_shape = p->expanded_shape; + s->R = p->R; + if (p->use_aux_channel) + s->R += 200; + s->conv_nb_states = p->conv_nb_states; + s->use_non_linear = p->use_non_linear; + s->use_high_carrier = p->use_high_carrier; + memcpy(s->h, p->h, sizeof(s->h)); + + /* superframe & data frame size */ + S = s->S; + if (!s->use_high_carrier) { + d = S_tab[S][2]; + e = S_tab[S][3]; + } else { + d = S_tab[S][4]; + e = S_tab[S][5]; + } + s->symbol_rate = 2400.0 * (float)S_tab[S][0] / (float)S_tab[S][1]; + s->carrier_freq = s->symbol_rate * (float)d / (float)e; + + s->J = S_tab[S][6]; + s->P = S_tab[S][7]; + s->N = (s->R * 28) / (s->J * 100); + /* max length of a mapping frame (no need for table 8 as in the spec !) */ + s->b = s->N / s->P; + if ((s->b * s->P) < s->N) s->b++; + s->r = s->N - (s->b - 1) * s->P; + + /* aux channel */ + if (p->use_aux_channel) + s->W = 15 - s->J; /* no need to test as in the spec ! */ + else + s->W = 0; /* no aux channel */ + + /* mapping parameters */ + s->q = 0; + if (s->b <= 12) { + s->K = 0; + } else { + s->K = s->b - 12; + while (s->K >= 32) { + s->K -= 8; + s->q++; + } + } + + /* XXX: use integer arith ! */ + if (!s->expanded_shape) { + s->M = (int) ceil(pow(2.0, s->K / 8.0)); + } else { + s->M = (int) rint(1.25 * pow(2.0, s->K / 8.0)); + } + s->L = 4 * s->M * (1 << s->q); + +#ifdef DEBUG + printf("S_index=%d (S=%0.0f carrier=%0.0f)\n" + "R=%d J=%d P=%d N=%d b=%d r=%d W=%d\n", + s->S, s->symbol_rate, s->carrier_freq, + s->R, s->J, s->P, s->N, s->b, s->r, s->W); + printf("K=%d q=%d M=%d L=%d\n", s->K, s->q, s->M, s->L); +#endif + + build_constellation(s); + + build_rings(s); + + s->baud_num = baud_tab[S][0]; + s->baud_denom = baud_tab[S][1]; + + if (transmit) { + build_tx_filter(s); + } else { + build_rx_filter(s); + agc_init(s); + s->phase_4d = 0; + } + + s->Z_1 = 0; + s->U0 = 0; /* trellis coder memory */ + memset(s->x, 0, sizeof(s->x)); + s->half_data_frame_count = 0; + s->sync_count = 0; + s->conv_reg = 0; + s->scrambler_reg = 0; + + s->mapping_frame = 0; /* mapping frame counters */ + s->acnt = 0; + s->rcnt = 0; + + return 0; +} + +/* shift by n & round toward zero */ +static inline int shr_round0(int val, int n) +{ + int offset; + + offset = (1 << (n-1)) - 1; + if (val >= 0) { + val = (val + offset) >> n; + } else { + val = -val; + val = (val + offset) >> n; + val = -val; + } + return val; +} + +/* clamp a between -v & v */ +static inline int clamp(int a, int v) +{ + if (a > v) + return v; + else if (a < -v) + return -v; + else + return a; +} + +/* compute the next state in the trellis (Y[0] is ignored) */ +static int trellis_next_state(int conv_nb_states, int conv_reg, int trans) +{ + int i,Y0; + int Y[5]; + + Y[1] = (trans & 1); + Y[2] = ((trans >> 1) & 1); + Y[4] = ((trans >> 2) & 1); + Y[3] = ((trans >> 3) & 1); + + Y0 = conv_reg & 1; + switch(conv_nb_states) { + case 16: + /* figure 10 */ + conv_reg ^= (Y[1] << 1) | (Y[2] << 2) | ((Y[2] ^ Y0) << 3) | (Y0 << 4); + conv_reg >>= 1; + break; + case 32: + /* figure 11 */ + conv_reg ^= (Y[2] << 1) | (Y[4] << 2) | (Y[1] << 3) | (Y[2] << 4) | (Y0 << 5); + conv_reg >>= 1; + break; + default: + case 64: + /* figure 12 */ + { + int r[6], s[6], tmp1, tmp2; + + for(i=0;i<6;i++) r[i] = (conv_reg >> i) & 1; + + s[0] = r[1] ^ r[3] ^ Y[2]; + s[1] = r[0]; + s[2] = r[3]; + tmp2 = Y[1] ^ r[4]; + s[3] = r[3] ^ tmp2; + tmp1 = r[4] ^ r[5]; + s[4] = r[2] ^ Y[3] ^ (r[3] & Y[2]) ^ tmp1; + s[5] = Y[4] ^ tmp1 ^ (r[3] & tmp2); + + conv_reg = 0; + for(i=0;i<6;i++) conv_reg |= s[i] << i; + } + break; + } + return conv_reg; +} + +/* (§ 9.6.3) trellis encoder, return U0 */ + +static int trellis_encoder(V34DSPState *s, int c0, int yy[2][2]) +{ + int v0, ss[2][3], Y[5], i, trans; + + /* convolutional coder */ + + /* (§ 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 = yy[i][0]; + y = yy[i][1]; + x = ((x + 3) >> 1) & 3; + y = ((y + 3) >> 1) & 3; + 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]; + + /* compute the next trellis state */ + trans = (Y[3] << 3) | (Y[4] << 2) | (Y[2] << 1) | Y[1]; + + s->conv_reg = trellis_next_state(s->conv_nb_states, s->conv_reg, trans); + Y[0] = s->conv_reg & 1; + + /* super frame synchronisation pattern */ + if (s->sync_count == 0) { + v0 = (SYNC_PATTERN >> (15 - s->half_data_frame_count)) & 1; + } else { + v0 = 0; + } + + return Y[0] ^ c0 ^ v0; +} + + +static int get_bit(V34DSPState *s) +{ + int b, poly; + + b = s->get_bit(s->opaque); + if (b == -1) b = 1; + if (s->calling) + poly = V34_GPC; + else + poly = V34_GPA; + b = scramble_bit(s, b, poly); + return b; +} + +/* auxilary channel bit */ +static int aux_get_bit(V34DSPState *s) +{ + return 0; +} + +/* encode size bits into the corresponding symbols of the constellation */ +/* return exactly 8 baseband complex samples */ +static void encode_mapping_frame(V34DSPState *s) +{ + int m[4][2], r0; /* rings */ + u8 I[3][4]; + int Q[4][2], Z[2], Y[2][2]; + u8 *ptr; + int t,i,j,k,x,y,x1,y1,w,mp_size; + int u_re, u_im, p_re, p_im, c_re, c_im, C0, x_re, x_im, xp_re, xp_im; + u8 data[MAX_MAPPING_FRAME_SIZE]; + + /* compute mapping frame size */ + s->rcnt += s->r; + if (s->rcnt < s->P) { + mp_size = s->b - 1; + } else { + s->rcnt -= s->P; + mp_size = s->b; + } + + /* send an auxilary channel bit if needed */ + s->acnt += s->W; + if (s->acnt < s->P) { + data[0] = get_bit(s); + } else { + s->acnt -= s->P; + data[0] = aux_get_bit(s); + } + + for(i=1;ib <= 12) { + /* (§ 9.3.2) simple case: no shell mapping */ + memset(m, 0, sizeof(m)); + for(i=0;ib;i++) ((u8 *)I)[i] = data[i]; + for(i=s->b;i<12;i++) ((u8 *)I)[i] = 0; + memset(Q, 0, sizeof(Q)); + } else { + /* (§ 9.3.1) */ + /* leave one bit if low frame */ + int n; + + n = s->K; + if (mp_size < s->b) n--; + + ptr = data; + r0 = 0; + for(i=0;iq;i++) t |= *ptr++ << i; + Q[j][0] = t; + + t = 0; + for(i=0;iq;i++) t |= *ptr++ << i; + Q[j][1] = t; + } + } + + if (s->b < 56) + w = 1; + else + w = 2; + + for(j=0;j<4;j++) { + /* for each 4D symbol */ + + /* (§ 9.5) differential coding */ + Z[0] = (I[1][j] + 2 * I[2][j] + s->Z_1) & 3; + s->Z_1 = Z[0]; + + /* (§ 9.6.1) mapping to 2D symbols */ + Z[1] = (Z[0] + 2 * I[0][j] + s->U0) & 3; + + C0 = 0; /* for trellis coding */ + for(i=0;i<2;i++) { + t = Q[j][i] + (m[j][i] << s->q); + + assert(t >= 0 && t < L_MAX/4); + x1 = s->constellation[t][0]; + y1 = s->constellation[t][1]; + /* rotation by Z[i] * 90 degress clockwise */ + rotate_clockwise(x, y, x1, y1, Z[i]); + u_re = x; + u_im = y; + + /* (§ 9.6.2) precoder */ + x = 0; + y = 0; + for(k=0;k<3;k++) { + x += s->x[k][0] * s->h[k][0] - s->x[k][1] * s->h[k][1]; + y += s->x[k][1] * s->h[k][0] + s->x[k][0] * s->h[k][1]; + } + /* round to 2^-7 */ + p_re = shr_round0(x, 14); + p_im = shr_round0(y, 14); + + /* compute c(n) */ + c_re = shr_round0(p_re, 7 + w) << w; + c_im = shr_round0(p_im, 7 + w) << w; + C0 += c_re + c_im; + + Y[i][0] = clamp(u_re + c_re, 255); + Y[i][1] = clamp(u_im + c_im, 255); + + x_re = (Y[i][0] << 7) - p_re; + x_im = (Y[i][1] << 7) - p_im; + + for(k=2;k>=1;k--) { + s->x[k][0] = s->x[k-1][0]; + s->x[k][1] = s->x[k-1][1]; + } + s->x[0][0] = x_re; + s->x[0][1] = x_im; + + /* (§ 9.7) non linear encoder */ + + if (!s->use_non_linear) { + xp_re = x_re; + xp_im = x_im; + } else { + int x2; + float dzeta,theta; + /* XXX: average power ? */ + + x2 = (x_re * x_re + x_im * x_im) >> 7; + dzeta = 0.3125 /* x2 / 128.0 */; + theta = (1 + dzeta / 6.0 + dzeta * dzeta / 120.0); + + xp_re = rint(theta * x_re); + xp_im = rint(theta * x_im); + } + + put_sym(s, xp_re, xp_im); + } + s->U0 = trellis_encoder(s, (C0 >> 1) & 1, Y); + + /* 4D symbol count & data frame count for synchronisation */ + if (++s->sync_count == 2*s->P) { + s->sync_count = 0; + if (++s->half_data_frame_count == 2*s->J) { + s->half_data_frame_count = 0; + } + } + } + + if (++s->mapping_frame >= s->P) { + /* new data frame */ + s->mapping_frame = 0; + s->rcnt = 0; + s->acnt = 0; + } +} + + +/* put a new baseband symbol in the tx queue */ +static void put_sym(V34DSPState *s, int si, int sq) +{ + s->tx_buf[s->tx_buf_ptr][0] = (si * s->tx_amp) >> 7; + s->tx_buf[s->tx_buf_ptr][1] = (sq * s->tx_amp) >> 7; + + s->tx_buf_ptr = (s->tx_buf_ptr + 1) & (TX_BUF_SIZE - 1); + s->tx_buf_size++; + + assert(s->tx_buf_size <= TX_BUF_SIZE); +} + + +/* write at most nb samples, return the number of samples + written. Stops if no more baseband symbols in tx queue */ +static int V34_baseband_to_carrier(V34DSPState *s, + s16 *samples, unsigned int nb) +{ + int si, sq, ph, i, j, k; + + for(i=0;itx_buf_size < s->tx_filter_wsize) + break; + + /* apply the spectrum shaping filter */ + ph = s->baud_phase; + si = sq = 0; + for(j=0;jtx_filter_wsize;j++) { + k = (s->tx_outbuf_ptr - j - 1) & + (TX_BUF_SIZE - 1); + si += s->tx_buf[k][0] * s->tx_filter[ph]; + sq += s->tx_buf[k][1] * s->tx_filter[ph]; + ph += s->baud_denom; + } + si = si >> 14; + sq = sq >> 14; + if ( si != (short)si || sq != (short)sq) { + printf("error %d %d\n", si, sq); + } + // printf("M: phase=%04X %d %d\n", s->baud_phase, si, sq); + /* get next baseband symbols */ + s->baud_phase += s->baud_num; + if (s->baud_phase >= s->baud_denom) { + s->baud_phase -= s->baud_denom; + s->tx_outbuf_ptr = (s->tx_outbuf_ptr + 1) & (TX_BUF_SIZE - 1); + s->tx_buf_size--; + } + + /* center on the carrier */ + samples[i] = (si * dsp_cos(s->carrier_phase) - + sq * dsp_cos((PHASE_BASE/4) - s->carrier_phase)) >> COS_BITS; + s->carrier_phase += s->carrier_incr; + } + return i; +} + +#define S_POWER (1 + 1) +#define TRN4_POWER (1 + 1) +#define TRN16_POWER ((1 + 1 + 2*(9 + 1) + 9 + 9) / 4.0) + +/* compute the normalized amplitude */ +#define CALC_AMP(x) (int)( (128.0 * 128.0) / sqrt(x) ) + +/* send the V34 S sequence, duration: 128 T */ +static void V34_send_S(V34DSPState *s) +{ + int i; + + /* transmit amplitude multiplier */ + s->tx_amp = CALC_AMP(S_POWER); + + for(i=0;i<64;i++) { + put_sym(s, 128, 128); /* 0 deg */ + put_sym(s, -128, 128); /* -90 deg */ + } +} + +/* send the V34 S bar sequence, duration: 16 T */ +static void V34_send_Sinv(V34DSPState *s) +{ + int i; + + s->tx_amp = CALC_AMP(S_POWER); + for(i=0;i<8;i++) { + put_sym(s, -128, -128); /* 180 deg */ + put_sym(s, 128, -128); /* 90 deg */ + } +} + +/* send the PP sequence, duration: 288 T */ +static void V34_send_PP(V34DSPState *s) +{ + int k,p; + + s->tx_amp = 128; + + /* 6 periods */ + for(p=0;p<6;p++) { + for(k=0;kcalling) + poly = V34_GPC; + else + poly = V34_GPA; + + if (s->is_16states) + s->tx_amp = CALC_AMP(TRN16_POWER); + else + s->tx_amp = CALC_AMP(TRN4_POWER); + + for(i=0;i<1024;i++) { + I1 = scramble_bit(s, 1, poly); + I2 = scramble_bit(s, 1, poly); + if (s->is_16states) { + Q1 = scramble_bit(s, 1, poly); + Q2 = scramble_bit(s, 1, poly); + q = (Q2 << 1) | Q1; + } else { + q = 0; + } + x1 = s->constellation[q][0] << 7; + y1 = s->constellation[q][1] << 7; + z = (I2 << 1) | I1; + rotate_clockwise(x, y, x1, y1, z); + put_sym(s, x, y); + } + s->Z_1 = z; /* the last value z is used for the next sequence */ +} + +void put_bits(u8 **pp, int n, int bits) +{ + u8 *p; + int i; + + p = *pp; + for(i=n-1;i>=0;i--) { + *p++ = (bits >> i) & 1; + } + *pp = p; +} + +/* from § 10.1.2.3.2 */ +int calc_crc(u8 *buf, int size) +{ + int crcinv, crc,i,b; + + crc = 0xffff; + + for(i=0;i> 1) ^ ((b << 15) | (b << 10) | (b << 3)); + } + + /* invert the order of the crc bits (could be done while + computing, but who cares ? */ + crcinv = 0; + for(i=0;i<16;i++) { + crcinv |= ((crc >> i) & 1) << (15-i); + } + return crcinv; +} + +/* modulate an MP sequence */ +static void V34_mod_MP(V34DSPState *s, u8 *buf, int size, int is_16states) +{ + int x,y,x1,y1,z,poly,I1,I2,Q1,Q2,q; + u8 *p; + + if (s->calling) + poly = V34_GPC; + else + poly = V34_GPA; + + if (is_16states) + s->tx_amp = CALC_AMP(TRN16_POWER); + else + s->tx_amp = CALC_AMP(TRN4_POWER); + + p = buf; + z = s->Z_1; + while ((p - buf) < size) { + I1 = scramble_bit(s, *p++, poly); + I2 = scramble_bit(s, *p++, poly); + if (is_16states) { + Q1 = scramble_bit(s, *p++, poly); + Q2 = scramble_bit(s, *p++, poly); + q = (Q2 << 1) | Q1; + } else { + q = 0; + } + x1 = s->constellation[q][0] << 7; + y1 = s->constellation[q][1] << 7; + z = (((I2 << 1) | I1) + z) & 3; + rotate_clockwise(x, y, x1, y1, z); + put_sym(s, x, y); + } + s->Z_1 = z; +} + + +/* send MP sequence. 'type' select its type (0 or 1). 'do_ack' selects + if it is an acknowledge sequence */ +static void V34_send_MP(V34DSPState *s, int type, int do_ack) +{ + u8 buf[188],*p; + int i,j,crc; + + p = buf; + put_bits(&p, 17, 0x1ffff); /* frame sync */ + put_bits(&p, 1, 0); /* start bit */ + put_bits(&p, 1, type); /* type: 1 */ + put_bits(&p, 1, 0); /* reserved */ + put_bits(&p, 4, 12); /* call to answer max rate: 28800 */ + put_bits(&p, 4, 12); /* answer to call max rate: 28800 */ + put_bits(&p, 1, 0); /* no aux channel */ + put_bits(&p, 2, 0); /* 0=16 state trellis */ + put_bits(&p, 1, 0); /* non linear encoder disabled */ + put_bits(&p, 1, 0); /* constellation shaping, 0=minimum, 1=expanded */ + put_bits(&p, 1, do_ack); /* acknowledge bit */ + + put_bits(&p, 1, 0); /* start bit */ + put_bits(&p, 15, 0x7ff8); /* all rates enabled up to 28800 */ + put_bits(&p, 1, 1); /* asymetric data rate enable */ + + if (type == 1) { + for(i=0;i<3;i++) { + for(j=0;j<2;j++) { + put_bits(&p, 1, 0); /* start bit */ + put_bits(&p, 16, s->h[i][j]); /* precoding coef */ + } + } + } + + put_bits(&p, 1, 0); /* start bit */ + put_bits(&p, 16, 0); /* reserved by ITU */ + + put_bits(&p, 1, 0); /* start bit */ + + crc = calc_crc(buf + 17, p - (buf+17)); + put_bits(&p, 16, crc); + + put_bits(&p, 1, 0); /* fill bit */ + if (type == 0) { + put_bits(&p, 2, 0); /* fill bit */ + } + + /* now we transmit the buffer */ + V34_mod_MP(s, buf, p - buf, s->is_16states); +} + +/* send E sequence */ +static void V34_send_E(V34DSPState *s) +{ + u8 buf[20]; + + memset(buf, 1, 20); + + V34_mod_MP(s, buf, 20, s->is_16states); +} + +/* J sequence */ +#define J4POINTS 0x0991 +#define J16POINTS 0x0D91 +#define JEND 0xF991 + +static void V34_send_J(V34DSPState *s, int length) +{ + int i,val; + u8 buf[16],*p; + + if (s->is_16states) + val = J16POINTS; + else + val = J4POINTS; + p = buf; + put_bits(&p, 16, val); + + for(i=0;istate) { + case V34_STARTUP3_WAIT_J: + /* silence */ + if (nb > 0) { + *samples++ = 0; + nb--; + } + break; + +#if 0 + case V34_START: + /* 600 bps DPSK modulation */ + + +#endif + default: + /* V34 modulation */ + while (s->tx_buf_size >= s->tx_filter_wsize && nb > 0) { + n = V34_baseband_to_carrier(s, samples, nb); + samples += n; + nb -= n; + } + break; + } + + if (nb == 0) break; + + /* protocol state machine */ + + switch(s->state) { +#if 0 + /* phase 2 */ + case V34_START: + V34_send_info0(s, 0); + break; +#endif + + /* phase 3 */ + case V34_STARTUP3_S1: + V34_send_S(s); + s->state = V34_STARTUP3_SINV1; + break; + case V34_STARTUP3_SINV1: + V34_send_Sinv(s); + s->state = V34_STARTUP3_S2; + break; + case V34_STARTUP3_S2: + V34_send_S(s); + s->state = V34_STARTUP3_SINV2; + break; + case V34_STARTUP3_SINV2: + V34_send_Sinv(s); + s->state = V34_STARTUP3_PP; + break; + case V34_STARTUP3_PP: + V34_send_PP(s); + s->state = V34_STARTUP3_TRN; + break; + case V34_STARTUP3_TRN: + V34_send_TRN(s); + // s->state = V34_STARTUP3_J; + break; + case V34_STARTUP3_J: + V34_send_J(s, 10); /* XXX: must wait for S on other end */ + if (s->calling) { + s->state = V34_STARTUP3_JP; + } else { + /* the anwser modem wait for the S signal */ + s->state = V34_STARTUP3_WAIT_J; + } + break; + case V34_STARTUP3_JP: + V34_send_JP(s); + //s->state = V34_STARTUP4_TRN; + s->state = V34_STARTUP3_S1; + break; + + case V34_STARTUP3_WAIT_J: + if (s->J_received) + s->state = V34_STARTUP4_S; + break; + + /* phase 4 */ + case V34_STARTUP4_S: + V34_send_S(s); + s->state = V34_STARTUP4_WAIT_JP; + break; + case V34_STARTUP4_WAIT_JP: + if (s->JP_received) + s->state = V34_STARTUP4_SINV; + break; + case V34_STARTUP4_SINV: + V34_send_S(s); + s->state = V34_STARTUP4_TRN; + break; + + case V34_STARTUP4_TRN: + V34_send_TRN(s); + s->state = V34_STARTUP4_MP; + break; + case V34_STARTUP4_MP: + V34_send_MP(s, 1, 0); /* type 1, no ack */ + s->state = V34_STARTUP4_MPP; + break; + case V34_STARTUP4_MPP: + V34_send_MP(s, 1, 1); /* type 1, ack */ + s->state = V34_STARTUP4_E; + break; + case V34_STARTUP4_E: + V34_send_E(s); + s->state = V34_DATA; + break; + case V34_DATA: + /* compute the next 8 baseband symbols */ + encode_mapping_frame(s); + break; + } + } +} + +static void V34_mod_init(V34DSPState *s, V34State *p) +{ + V34_init_low(s, p, 1); + s->state = V34_STARTUP3_S1; + s->JP_received = 1; + // s->state = V34_DATA; + s->is_16states = 0; /* use 16 states */ +} + +/*****************************************************/ +/* Here begins the real fun ! */ + +static inline int tcm_decision(int level, int sample) +{ + int x, xs; + + /* find the 4x4 subset */ + x = (sample + (7 * 128) - (level << 8)) >> 10; + /* convert back to sample */ + xs = (((x<<2) + level - 2) << 8) + 128; + return xs; +} + +static int tcm_dist(int level, int sample) +{ + int xs, e; + + xs = tcm_decision(level,sample); + + e = sample - xs; +#if 0 + printf("level=%d sample=%d(%d) xs=%d e=%d e1=%d e2=%d\n", + level, sample, (sample >> 8) * 2 + 1, xs, e, + sample - (xs + 1024), sample-(xs - 1024)); +#endif + return (e*e) >> 8; +} + +/* Viterbi decoder for the V34 trellis coded modulation */ + +static void trellis_decoder(V34DSPState *s, s16 yout[2][2], s16 yy[2][2], + int *mse) +{ + int i, j, k, n, nbbt, nb_trans, state, next_state, error, trellis_ptr; + int error_table[32],decision_table[32],emin,jmin,u0,x,y; + u8 *p,*q; + + trellis_ptr = s->trellis_ptr; + + /* compute the number of bits used in the transitions from each state */ + switch(s->conv_nb_states) { + case 16: + nbbt = 2; + p = &trellis_trans_4[0][0]; + break; + case 32: + nbbt = 3; + p = &trellis_trans_8[0][0]; + break; + default: + nbbt = 4; + p = &trellis_trans_16[0][0]; + break; + } + nb_trans = 1 << nbbt; + + /* write a previous decoded symbol : extract a decoded bit from + the beginning of a path */ + + k = trellis_ptr; + k--; + if (k < 0) k = TRELLIS_LENGTH-1; + j = 0; /* arbitrary path selected : all the paths converge to same + decoded bits*/ + for(i=0;i<(TRELLIS_LENGTH-1);i++) { + j = s->state_path[j][k]; + k--; + if (k < 0) k = TRELLIS_LENGTH-1; + } + u0 = s->u0_memory[trellis_ptr]; + q = p + (s->state_decision[j][k] * 4); +#if 1 + yout[0][0] = tcm_decision(q[0], s->state_memory[trellis_ptr][0]); + yout[0][1] = tcm_decision(q[1], s->state_memory[trellis_ptr][1]); + yout[1][0] = tcm_decision(q[2], s->state_memory[trellis_ptr][2]); + yout[1][1] = tcm_decision(q[3], s->state_memory[trellis_ptr][3]); + /* undo the rotation */ + if ((s->state_decision[j][k] >> 7)) { + x = yout[1][1]; + y = - yout[1][0]; + yout[1][0] = x; + yout[1][1] = y; + } + /* rotate only if u0 is set */ + if (u0 ^ (s->state_decision[j][k] >> 7)) { + x = - yout[1][1]; + y = yout[1][0]; + yout[1][0] = x; + yout[1][1] = y; + } +#else + /* no trellis */ + yout[0][0] = s->state_memory[trellis_ptr][0]; + yout[0][1] = s->state_memory[trellis_ptr][1]; + yout[1][0] = s->state_memory[trellis_ptr][2]; + yout[1][1] = s->state_memory[trellis_ptr][3]; +#endif + /* compute the mean square error (normalized to 2^7) */ + *mse = (dsp_sqr(yout[0][0] - s->state_memory[trellis_ptr][0]) + + dsp_sqr(yout[0][1] - s->state_memory[trellis_ptr][1]) + + dsp_sqr(yout[1][0] - s->state_memory[trellis_ptr][2]) + + dsp_sqr(yout[1][1] - s->state_memory[trellis_ptr][3])) >> 7; + + s->state_memory[trellis_ptr][0] = yy[0][0]; + s->state_memory[trellis_ptr][1] = yy[0][1]; + s->state_memory[trellis_ptr][2] = yy[1][0]; + s->state_memory[trellis_ptr][3] = yy[1][1]; + + /* compute the error table */ + /* XXX: may be optimized by using the algebraic properties of the mapping */ + n = 128 >> nbbt; + jmin = 0; /* no warning */ + for(i=0;i<(nb_trans*2);i++) { + emin = 0x7fffffff; + for(j=0;ju0_memory[trellis_ptr] = (jmin >= nb_trans); + + /* init the error table to +infinity */ + for(state=0;stateconv_nb_states;state++) { + s->state_error1[state] = 0x7fffffff; + } + + for(state=0;stateconv_nb_states;state++) { + /* select the value of y0 depending on the current state */ + /* for each state, we update the next state entry by selecting + the shortest path */ + /* XXX: should handle error overflow */ + if (state & 1) + n = nb_trans; + else + n = 0; + for(j=0;jconv_nb_states, state, j); + error = s->state_error[state] + error_table[j + n]; + if (error < s->state_error1[next_state]) { + s->state_error1[next_state] = error; + s->state_decision[next_state][trellis_ptr] = decision_table[j + n]; + s->state_path[next_state][trellis_ptr] = state; + } + } + } + + /* XXX: this copy is not needed. Permute the two tables */ + memcpy(s->state_error, s->state_error1, sizeof(s->state_error)); + + trellis_ptr = (trellis_ptr + 1) % TRELLIS_LENGTH; + s->trellis_ptr = trellis_ptr; +} + +static void put_bit(V34DSPState *s, int b) +{ + int poly; + + if (!s->calling) + poly = V34_GPC; + else + poly = V34_GPA; + b = unscramble_bit(s, b, poly); + // printf("recv: %d\n", b); + s->put_bit(s->opaque, b); +} + +/* auxilary channel bit */ +static void aux_put_bit(V34DSPState *s, int b) +{ + /* not used now */ +} + +static void decode_mapping_frame(V34DSPState *s, s16 rx_mapping_frame[8][2]) +{ + int m[4][2], r0; /* rings */ + u8 I[3][4]; + int Q[4][2], Z[2]; + u8 *ptr; + int t,i,j,x,y,n,mp_size,xout_ptr; + u8 data[MAX_MAPPING_FRAME_SIZE]; + + xout_ptr = 0; + for(j=0;j<4;j++) { + /* for each 4D symbol */ + + for(i=0;i<2;i++) { + x = rx_mapping_frame[xout_ptr][0]; + y = rx_mapping_frame[xout_ptr][1]; + xout_ptr++; + + /* decision */ + x = (x >> 8) * 2 + 1; + x = clamp(x, C_RADIUS); + y = (y >> 8) * 2 + 1; + y = clamp(y, C_RADIUS); + + t = s->constellation_to_code[(x+C_RADIUS) >> 1][(y+C_RADIUS) >> 1]; + /* mapping to the symbol */ + Z[i] = t >> 14; + t = t & 0xff; + + Q[j][i] = t & ((1 << s->q)-1); + m[j][i] = t >> s->q; + } + + t = (Z[0] - s->Z_1) & 3; + s->Z_1 = Z[0]; + I[1][j] = t & 1; + I[2][j] = t >> 1; + + t = (Z[1] - Z[0]) & 3; + I[0][j] = t >> 1; + } + + /* compute mapping frame size */ + s->rcnt += s->r; + if (s->rcnt < s->P) { + mp_size = s->b - 1; + } else { + s->rcnt -= s->P; + mp_size = s->b; + } + + /* now everything is "decoded", we can write the data */ + ptr = data; + if (s->b <= 12) { + for(i=0;ib;i++) *ptr++ = ((u8 *)I)[i]; + } else { + r0 = rings_to_index(s, m); + + n = s->K; + if (mp_size < s->b) n--; + for(i=0;i> i) & 1; + + for(j=0;j<4;j++) { + ptr[0] = I[0][j]; + ptr[1] = I[1][j]; + ptr[2] = I[2][j]; + ptr += 3; + + t=Q[j][0]; + for(i=0;iq;i++) *ptr++ = (t >> i) & 1; + + t=Q[j][1]; + for(i=0;iq;i++) *ptr++ = (t >> i) & 1; + } + } + + /* send an auxilary channel bit if needed */ + s->acnt += s->W; + if (s->acnt < s->P) { + put_bit(s, data[0]); + } else { + s->acnt -= s->P; + aux_put_bit(s, data[0]); + } + + /* send all the decoded bits */ + for(i=1;imapping_frame >= s->P) { + /* new data frame */ + s->mapping_frame = 0; + s->rcnt = 0; + s->acnt = 0; + } +} + +static void baseband_decode(V34DSPState *s, int si, int sq) +{ + s16 y[2][2]; + static int delay = 0; + int mse,v0; + + lm_dump_qam(si / (10.0 * 128.0), sq / (10.0 * 128.0)); + + s->yy[s->phase_4d][0] = si; + s->yy[s->phase_4d][1] = sq; + + if (++s->phase_4d == 2) { + + trellis_decoder(s, y, s->yy , &mse); + s->phase_mse += mse; + s->phase_mse_cnt++; + if (s->phase_mse_cnt >= 8) { + s->phase_mse_cnt = 0; + s->phase_mse = 0; + } + + /* synchronization bit */ + if (s->sync_count == 0) { + v0 = (SYNC_PATTERN >> (15 - s->half_data_frame_count)) & 1; + } else { + v0 = 0; + } + + /* synchronization bit */ + if (++s->sync_count == 2*s->P) { + s->sync_count = 0; + if (++s->half_data_frame_count == 2*s->J) { + s->half_data_frame_count = 0; + } + } + + memcpy(&s->rx_mapping_frame[s->rx_mapping_frame_count][0], + &y[0][0], 4 * sizeof(s16)); + delay++; + if (delay > TRELLIS_LENGTH) { + + s->rx_mapping_frame_count += 2; + if (s->rx_mapping_frame_count == 8) { + /* a complete mapping frame was read */ + decode_mapping_frame(s, s->rx_mapping_frame); + s->rx_mapping_frame_count = 0; + } + } + s->phase_4d = 0; + } +} + + +/* AGC */ + +#define AGC_COEF 0.99 +#define AGC_BITS 24 + +static void agc_init(V34DSPState *s) +{ + s->agc_coef = (int) (AGC_COEF * (1 << AGC_BITS)); + s->agc_mem = 0; +} + +static void agc_estimate(V34DSPState *s, int sample) +{ + float power; + + s->agc_mem = s->agc_mem * AGC_COEF + (sample * sample); + + power = (float) s->agc_mem * (1.0 - AGC_COEF); + /* XXX: the constant here depends on the modulation parameters */ + // s->agc_gain = (16384.0/2 * 1495) / sqrt(power); + s->agc_gain = 16384.0 * 0.80; + + // lm_dump_agc(power / 16384.0); + lm_dump_agc(sqrt(power)); +} + +#if 0 +static void test_nyq(float *a_ptr, float *b_ptr, float f, float val) +{ + float a,b,c,d; + + a = *a_ptr; + b = *b_ptr; + c = 0.99 * cos(f); + d = 0.99 * sin(f); + + *a_ptr = a * c - b * d + val; + *b_ptr = a * d + b * c; +} + +static float al, bl, ah, bh; +static float f_low, f_high; +static float low_mem[2], low_coef[2]; +#endif + +#define SYNC_THR 32 + +/* Fast symbol timing recovery */ +static void v34_symbol_sync(V34DSPState *s, int spl) +{ + int a, b, c, v; +#if 0 + int tmp0, tmp1; + static s16 dc_filter[2]; + static s16 ac_filter[3]; +#endif + +#if 0 + { + static int k = 0; + float v; + + f_low = 2 * M_PI * (s->carrier_freq - s->symbol_rate / 2.0) / (3.0 * s->symbol_rate); + f_high = 2 * M_PI * (s->carrier_freq + s->symbol_rate / 2.0) / (3.0 * s->symbol_rate); + test_nyq(&al, &bl, f_low, spl); + test_nyq(&ah, &bh, f_high, spl); + } +#endif + /* sync_low_mem has an amplitude of about spl / ( 1 - a), so we + store it with a scaling of 2^6 to reduce its amplitude */ + + v = ((spl << 8) + s->sync_low_mem[0] * s->sync_low_coef[0] + + s->sync_low_mem[1] * s->sync_low_coef[1]) >> 14; + s->sync_low_mem[1] = s->sync_low_mem[0]; + s->sync_low_mem[0] = v; + + v = ((spl << 8) + s->sync_high_mem[0] * s->sync_high_coef[0] + + s->sync_high_mem[1] * s->sync_high_coef[1]) >> 14; + s->sync_high_mem[1] = s->sync_high_mem[0]; + s->sync_high_mem[0] = v; + + if (s->baud3_phase == 0) { + /* high & low nyquist filters for symbol timing recovery */ + + /* XXX: the shift should adapt to the power */ + a = (s->sync_low_mem[1] * s->sync_high_mem[1]) >> 14; + b = (s->sync_high_mem[1] * s->sync_low_mem[0]) >> 14; + c = (s->sync_low_mem[1] * s->sync_high_mem[0]) >> 14; + + v = (a * s->sync_A + b * s->sync_B + c * s->sync_C) >> 14; + printf("v=%d\n", v); + s->baud_phase -= v << 3; + +#if 0 + /* DC filter h(z) = z - z^-2 */ + tmp0 = v - dc_filter[1]; + dc_filter[1] = dc_filter[0]; + dc_filter[0] = v; + + /* AC filter h(z) = z + z^-3 */ + tmp1 = tmp0 + ac_filter[2]; + ac_filter[2] = ac_filter[1]; + ac_filter[1] = ac_filter[0]; + ac_filter[0] = tmp0; + + if (tmp1 < -SYNC_THR) + tmp1 = -SYNC_THR; + else if (tmp1 > SYNC_THR) + tmp1 = SYNC_THR; + else + tmp1 =0; +#endif +#if 1 + // lm_dump_sample(CHANNEL_SAMPLE, v); + + // printf("corr=%0.0f\n", + // corr * 32.0 / 100.0); +#else + printf("al=%0.1f ah=%0.1f al1=%0.1f ah1=%0.1f\n", + ah / 100.0, bh / 100.0, + (s->sync_high_mem[0] - s->sync_high_mem[1] * 0.99 * cos(f_high)) * 32.0 / 100.0, + (s->sync_high_mem[1] * 0.99 * sin(f_high)) * 32.0 / 100.0); +#endif + } +} + + +/* equalize & adapt the equalizer */ +static int v34_equalize(V34DSPState *s, + int *ri_ptr, int *rq_ptr, int spl) +{ + int p,q,i; + int ri, rq, fi, fq, q_ri, q_rq, ei, eq, ei1, eq1, si, sq; + int cosw, sinw, dphi, norm; + + /* add the sample in the equalizer ring buffer */ + p = s->eq_buf_ptr; + + s->eq_buf[p] = spl; + + if (++p == EQ_SIZE) + p = 0; + s->eq_buf_ptr = p; + + if (s->baud3_phase != 0) + return 0; + + /* apply the equalizer filter to the data */ + ri = rq = 0; + q = p; + for(i=0;ieq_filter[i][0] >> 16; + fq = s->eq_filter[i][1] >> 16; + + ri += fi * s->eq_buf[q]; + rq += fq * s->eq_buf[q]; + + q++; + if (q == EQ_SIZE) + q = 0; + } + si = ri >> 14; + sq = rq >> 14; + + /* rotate by the carrier phase */ + /* translate back to baseband */ + + cosw = dsp_cos(s->carrier_phase); + sinw = - dsp_cos((PHASE_BASE/4) - s->carrier_phase); + ri = ( si * cosw - sq * sinw ) >> COS_BITS; + rq = ( si * sinw + sq * cosw ) >> COS_BITS; + + *ri_ptr = ri; + *rq_ptr = rq; + + /* compute the error */ + + /* quantification */ + q_ri = ((ri >> 8) * 2 + 1) << 7; + q_rq = ((rq >> 8) * 2 + 1) << 7; + + /* error computation */ + ei1 = - (ri - q_ri); + eq1 = - (rq - q_rq); + + /**** phase tracking */ + + /* normalized derivative of the phase shift */ + /* XXX: avoid sqrt : slow !!! */ + norm = (int) sqrt(ri * ri + rq * rq ); + if (norm > 0) { + dphi = (ri * eq1 - rq * ei1) / norm; + } else { + dphi = 0; + } + s->carrier_phase += s->carrier_incr - dphi; + + /* remodulate (because the equalizer is done before converting to + baseband) */ + ei = ( ei1 * cosw + eq1 * sinw ) >> COS_BITS; + eq = ( - ei1 * sinw + eq1 * cosw ) >> COS_BITS; + +#if 1 + /* update the coefficients with the error */ + q = p; + for(i=0;ieq_buf[q]; + dq = eq * s->eq_buf[q]; + + s->eq_filter[i][0] += (di >> s->eq_shift) * 16; + s->eq_filter[i][1] += (dq >> s->eq_shift) * 16; + + q++; + if (q == EQ_SIZE) + q = 0; + } +#endif + + lm_dump_equalizer(s->eq_filter, 1 << 30, EQ_SIZE); + + return 1; +} + +static void V34_demod(V34DSPState *s, + const s16 *samples, unsigned int nb) +{ + int si, sq, i, j, k , ph, spl; + int v, frac, ph1; + + for(i=0;iagc_gain) >> 14; + + /* insert the new sample in the ring buffer */ + s->rx_buf1[s->rx_buf1_ptr] = spl; + s->rx_buf1_ptr = (s->rx_buf1_ptr + 1) & (RX_BUF1_SIZE-1); + + /* sample rate convertion, timing correction & matched filter + (root raised cosine) */ + s->baud_phase += s->baud_num; + while (s->baud_phase >= s->baud_denom) { + s->baud_phase -= s->baud_denom; + + ph = s->baud_phase; + si = 0; + for(j=0;jrx_filter_wsize;j++) { + k = (s->rx_buf1_ptr - s->rx_filter_wsize + j) & (RX_BUF1_SIZE-1); + /* XXX: verify that there is no overflow */ + + /* interpolation of the filter coefficient */ + ph1 = ph >> 16; + frac = ph & 0xffff; + v = ((0x10000 - frac) * s->rx_filter[ph1] + + frac * s->rx_filter[ph1+1]) >> 16; + si += v * s->rx_buf1[k]; + ph += s->baud_num; + } + si = (si >> 14); + lm_dump_sample(CHANNEL_SAMPLESYNC, si / 32768.0); + + /* we have here EQ_FRAC = 3 symbols per baud */ + + switch(s->state) { + case V34_STARTUP3_WAIT_S1: + /* wait for the S signal */ + printf("waiting S1 %d\n", si); + /* XXX: find a better test ! */ + if (abs(si) > 13000) { + s->state = V34_STARTUP3_S1; + s->sym_count = 0; + } + break; + + case V34_STARTUP3_S1: + /* S signals are mainly used to recover the symbol clock */ + v34_symbol_sync(s, si); + if (++s->sym_count >= 128 * EQ_FRAC) { + s->state = V34_STARTUP3_SINV1; + s->sym_count = 0; + } + break; + case V34_STARTUP3_SINV1: + v34_symbol_sync(s, si); + if (++s->sym_count >= 16 * EQ_FRAC) { + s->state = V34_STARTUP3_S2; + s->sym_count = 0; + } + break; + + case V34_STARTUP3_S2: + v34_symbol_sync(s, si); + if (++s->sym_count >= 128 * EQ_FRAC) { + s->state = V34_STARTUP3_SINV2; + s->sym_count = 0; + } + break; + + case V34_STARTUP3_SINV2: + v34_symbol_sync(s, si); + if (++s->sym_count >= 100 * EQ_FRAC) { + s->state = V34_STARTUP3_PP; + s->sym_count = 0; + } + break; + + case V34_STARTUP3_PP: +#if 1 + /* PP is used to fast train the equalizer */ + + /* store the 144 samples at the middle of the PP + frame. We do this because we suppose in the fast + equalizer that the sequence is periodic */ + if (s->sym_count >= (120) * EQ_FRAC && + s->sym_count < (168) * EQ_FRAC) { + s->eq_buf[s->sym_count - (120) * EQ_FRAC] = si; + } + + if (s->sym_count == (168) * EQ_FRAC) { + /* XXX: this call takes a long time. Is it a + problem ? */ + V34_fast_equalize(s, s->eq_buf); + /* reset eq_buf to avoid potential problems when the + adaptive is started */ + memset(s->eq_buf, 0, sizeof(s->eq_buf)); + } + + if (++s->sym_count == 288 * EQ_FRAC) { + s->state = V34_STARTUP3_TRN; + } +#else + memmove(s->eq_buf, &s->eq_buf[1], 2 * EQ_SIZE); + s->eq_buf[EQ_SIZE - 1] = si; + + if ((++s->sym_count % EQ_SIZE) == 0) { + V34_fast_equalize(s, s->eq_buf); + lm_dump_equalizer(s->eq_filter, 1 << 30, EQ_SIZE); + } +#endif + + break; + + case V34_STARTUP3_TRN: + si = (float)si * 128.0 / CALC_AMP(TRN4_POWER); + if (v34_equalize(s, &si, &sq, si)) { + static int ptr = 0; + + if (++ptr > (28 * 2)) { + baseband_decode(s, si, sq); + } + } + break; + } + + + + if (++s->baud3_phase == EQ_FRAC) + s->baud3_phase = 0; + } + } +} + +static void V34_demod_init(V34DSPState *s, V34State *p) +{ + memset(s, 0, sizeof(V34DSPState)); + + V34_init_low(s, p, 0); + s->state = V34_STARTUP3_WAIT_S1; +} + +/* init the V34 constants. Should be launched once */ +void V34_static_init(void) +{ + V34eq_init(); +} + + +void V34_init(struct V34State *s, int calling) +{ + + +} + +int V34_process(struct V34State *s, s16 *output, s16 *input, int nb_samples) +{ + + + return 0; +} + +/* V34 test: half duplex with fixed parameters */ + +#define NB_SAMPLES 40 /* 5 ms */ + +static int test_get_bit(void *opaque) +{ + return 1; +} + +static int nb_bits, errors; + +static void test_put_bit(void *opaque, int bit) +{ + nb_bits++; + if (bit != 1) { + errors++; + } +} + +void V34_test(void) +{ + V34State s; + V34DSPState v34_rx, v34_tx; + int err; + 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(); + + /* fill the test V34 parameters */ +#if 0 + s.S = V34_S3200; + s.R = 28800; +#else + s.S = V34_S2400; + s.R = 19200; +#endif + s.expanded_shape = 0; + s.conv_nb_states = 16; + s.use_non_linear = 0; + s.use_high_carrier = 1; + s.use_aux_channel = 0; + memset(s.h, 0, sizeof(s.h)); + + f1 = fopen("cal.sw", "wb"); + if (f1 == NULL) { + perror("cal.sw"); + exit(1); + } + + s.calling = 1; + V34_mod_init(&v34_tx, &s); + v34_tx.opaque = NULL; + v34_tx.get_bit = test_get_bit; + + s.calling = 0; + V34_demod_init(&v34_rx, &s); + v34_rx.opaque = NULL; + v34_rx.put_bit = test_put_bit; + + nb_bits = 0; + errors = 0; + for(;;) { + if (lm_display_poll_event()) + break; + + V34_mod(&v34_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); + + V34_demod(&v34_rx, buf, NB_SAMPLES); + } + + fclose(f1); + + printf("errors=%d nb_bits=%d Pe=%f\n", + errors, nb_bits, (float) errors / (float)nb_bits); +} diff --git a/v34.h b/v34.h new file mode 100644 index 0000000..f5cf1d1 --- /dev/null +++ b/v34.h @@ -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 + diff --git a/v34eq.c b/v34eq.c new file mode 100644 index 0000000..f742516 --- /dev/null +++ b/v34eq.c @@ -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> 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;pre = (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 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> 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 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;ieq_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;ieq_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;ieq_filter[i][0] = (int)(tab[i].re * FRAC * FRAC / 12) << 16; + s->eq_filter[i][1] = (int)(tab[i].im * FRAC * FRAC / 12) << 16; + } +} diff --git a/v34gen.c b/v34gen.c new file mode 100644 index 0000000..7cb5016 --- /dev/null +++ b/v34gen.c @@ -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 +#include +#include +#include + +#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> 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;ical (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;nstate) { + case V34_P2_INFO0_SEND: + V34_send_info0( + + } + + /* demodulation */ + switch(s->state) { + case V34_P2_INFO0_SEND: + + + } +} + diff --git a/v34priv.h b/v34priv.h new file mode 100644 index 0000000..92cb76a --- /dev/null +++ b/v34priv.h @@ -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 + diff --git a/v8.c b/v8.c new file mode 100644 index 0000000..4bcac43 --- /dev/null +++ b/v8.c @@ -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;iphase_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;icos_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;ibuf[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;irx_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; +} diff --git a/v8.h b/v8.h new file mode 100644 index 0000000..c8ce166 --- /dev/null +++ b/v8.h @@ -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); diff --git a/v90.c b/v90.c new file mode 100644 index 0000000..564e920 --- /dev/null +++ b/v90.c @@ -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;jK) - 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;iucode_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;iK;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; it & ((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;iK;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;iM[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> 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 +#include +#include + +/* 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; +} diff --git a/v90priv.h b/v90priv.h new file mode 100644 index 0000000..872188c --- /dev/null +++ b/v90priv.h @@ -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