                        Talkline - How it works.
                        ~~~~~~~~~~~~~~~~~~~~~~~~

     Talkline is a sound of short duration, appended to the end of a message.
     In this fist version, up to 5 seconds of sound can be added.
     The purpose of this text file is to explain how the sound is coded
and appended to the message.   This first version is called version "A" and
has the following characterization:

1-Sampling. - The sound bandwidth supports human voice only and so is sampled
   at 5012.5Hz Hz, 8bits, with zero level at value 80h.

2-Compression -
   A - The signal is converted to 4 bits, first subtracting DC value (80h) and
       using a log (base 2) conversion of the absolute value:
            in           out
                0  ->  0
          0.   .1  ->  1
          2.   .3  ->  2
          4.   .7  ->  3
          8.  .15  ->  4
         16.  .31  ->  5
         32.  .63  ->  6
         64. .127  ->  7
       Now the signal is 4 bits with zero level at 0 (-16 to 15).

   B - The samples are packed 2 samples per byte, high nibble corresponding
       to first sample and the low nibble to next one

        EX:
        t = 0  0000hhhh  <-- 4 bit sample
            1  0000LLLL

                pack  ->  hhhhLLLL

   C - Run lenght encoded, using FFh as flag character.

       EX:
       00 32 84 84 84 84 84 84 34 35 FF 54 54 FE FE FE FE FE FE

       is converted to:
       00 32 FF 05 84 34 35 FF 01 FF 54 54 FF 06 FE

3-Coding -

        Mode A: The signal is coded using 7 bits, from 30h to AFh.

        EX:
        0 - AAAAaaaa      - >   0 - 0AAAAaaa   +  30h
        1 - BBBBbbbb            1 - 0BBBBbbb   +  30h
        2 - CCCCcccc            2 - 0CCCCccc   +  30h
        3 - DDDDdddd            3 - 0DDDDddd   +  30h
        4 - EEEEeeee            4 - 0EEEEeee   +  30h
        5 - FFFFffff            5 - 0FFFFfff   +  30h
        6 - GGGGgggg            6 - 0GGGGggg   +  30h
                                7 - 0abcdefg   +  30h

        Mode B: The signal is coded using 6 bits, from 30h to 70h.

        EX:
        0 - AAAAaaaa      - >   0 - 00AAAAaa   +  30h
        1 - BBBBbbbb            1 - 00BBBBbb   +  30h
        2 - CCCCcccc            2 - 00CCCCcc   +  30h
                                3 - 00aabbcc   +  30h


4-Saving - The result is appended to the end of message at 64 bytes per line,
  with a header:

   "[TALK]"  - 6 bytes - Identify a talkline
   1Bh,"[8m" - 4 bytes - ANSI command to disable output, to avoid trash on
               mail readers/terminal emulators without sound capabilities
               but with full ANSI suport.
   "A"       - 1 byte  -  A = coding in 7 bits
                          B = coding in 6 bits
                          Letters A to H are reserved.
   "00000"   - 5 bytes -  The size of the sound in bytes, after coding and
                          without line feeds (E3h).
   E3h       - 1 byte  -  The character used as LF by the QWK file format.

   .. follow first 64 bytes,E3h,  and so on...

=========================================================================

   As example, the routines, used  to compress and expand the voice signal.

//-----------------------------------------------------------------------
UINT WaveCompress (LPSTR Pk, LPSTR Wav, UINT nS)
//
//      Wav - intput signal
//      Pk  - output signal
//      nS  - number of data points
//
{
  register UINT k, T;
  char M;
  UINT Ncomp, nS;

  Log2(Wav, nS);                        ;4 bit convert
  Ncomp = nS >> 1;
//                                      pack data
  _asm  {
            LES   SI,Wav        ;ES:SI -> Wav
            MOV   DI,SI
            MOV   CX,Ncomp
            JCXZ  DONE
     L1:    MOV   AX,ES:[SI]
            SHL   AL,4          ;shift to HI nibble
            AND   AX,0FF0h      ;mask
            OR    AL,AH         ;merge
            STOSB               ;save
            ADD   SI,2
            LOOP  L1
     DONE:
         }
//
  nS = 0;
  Wav[Ncomp] = (char)(Wav[Ncomp-1]+1);  //make last different
  for(k=0; k<Ncomp; k++)                //run lenght encode
  {  T = 1;
     M = Wav[k];
     while (M == Wav[k+T])              //iqual next?
     { if(T == 253) break;
       else T = T + 1;                  //yes, bump T
     }
     if(T > 2 || M == '\xFF')
     { Wav[nS++] = 0xFF;                //mark
       Wav[nS++] =(char)T;              //total
       k = k + T - 1;                   //next
     }
     Wav[nS++] = M;                     //save data
  }
  Ncomp = Pack(Pk, Wav, nS);            //7 bits convert
  return Ncomp;
}
//-----------------------------------------------------------------
LONG WaveExpand (LPSTR Wav, LPSTR Pk, UINT nS)
//
//      Pk  - input signal
//      Wav - output signal
//      nS  - number of data points
//
{
  register UINT T, k;
  UINT Cnt;
  LONG nExp, Pt;

//
   nSamp = Unpack(Pk, Pk, nSamp);             // 8 bits convert
   nExp = 0;                                  // run lenght decode
   for(Pt=0; (UINT)Pt < nSamp; Pt++)
   {  if(Pk[Pt] == (char) 0xFF )
      { Cnt = (BYTE) Pk[++Pt];
        T = (char)Pk[++Pt];
        for(k = 0; k < Cnt; k++) Wav[nExp++] = (char)T;
      }
      else
      { Wav[nExp++] = Pk[Pt]; }
   }
   Cnt =(UINT)nExp;                           //unpack
   _asm {
            LES   DI, Wav
            MOV   BX,Cnt
            OR    BX,BX
            JZ    DONE
            MOV   SI,DI
            ADD   SI,BX         ;end of data
            ADD   BX,BX         ;end of unpacked data
     L1:    MOV   AL,ES:[SI]
            MOV   AH,AL         ;copy
            SHR   AL,4          ;do inverse operation
            AND   AX,0F0Fh     ;mask
            MOV   ES:[DI+BX],AX ;save
            DEC   SI
            SUB   BX,2
            JAE   L1            ;for all data points
     DONE:
        }
//
   nExp = nExp + nExp;
   Exp2(Wave, nExp);                            ;8 bit convert
   return nExp;
}
//=======================================================================

        Assebly routines to convert to and from 7 bits.

;-------------------------------------------------------------------------
;       UINT = Pack (LPSTR, LPSTR, UINT);
;
_Pack   PROC    FAR
        PUSH    BP
        MOV     BP,SP
        PUSHF
        CLD                     ;up
        PUSH    DS
        PUSH    SI
        PUSH    DI
        XOR     AX,AX           ;zero return value
        MOV     CX,[BP+14]      ;size  (up to 64k)
        JCXZ    PACK9
        LES     DI,[BP+6];
        LDS     SI,[BP+10];     ;DS:SI -> string
        XOR     BX,BX           ;byte count
        ADD     CX,SI           ;offset end of input string
        PUSH    DI              ;save it
;
PACK1:  MOV     DX,BX           ;insert a 0E3h each 64 bytes
        AND     DX,3FH          ;test
        JNZ     @F
        MOV     AL,0E3h         ;LF (on QWK...)
        STOSB                   ;insert
@@:     CMP     SI,CX           ;end of processing?
        JAE     PACK2           
        LODSW                   ;-- 1 & 2
        SHR     AL,1            ;-> 7, bit 0 to CY
        RCL     DH,1            ;CY to bit 0 on DH
        SHR     AH,1            ;repeat to next byte
        RCL     DH,1
        ADD     AX,3030h        ;translate to 30-A0h
        STOSW                   ;ok, save it
        LODSW
        SHR     AL,1            ;repeat for bytes 3 & 4
        RCL     DH,1
        SHR     AH,1
        RCL     DH,1
        ADD     AX,3030h
        STOSW
        LODSW
        SHR     AL,1            ;and 5 & 6
        RCL     DH,1
        SHR     AH,1
        RCL     DH,1
        ADD     AX,3030h
        STOSW
        LODSB
        SHR     AL,1            ;the last one...
        RCL     DH,1            ;
        MOV     AH,DH           ;save DH (7 saved bits)
        ADD     AX,3030h        ;
        STOSW                   ;
        ADD     BX,8            ;total
        JMP     PACK1
PACK2:  MOV     AX,DI           ;new position
        POP     DI              ;initial position
        SUB     AX,DI           ;return total processed bytes
PACK9:  XOR     DX,DX           ;AX:DX
        POP     DI
        POP     SI
        POP     DS
        POPF
        MOV     SP,BP
        POP     BP
        RET
_Pack   ENDP
;-------------------------------------------------------------
;       UINT = Unpack (LPSTR, LPSTR, UINT);
;
_Unpack PROC    FAR
        PUSH    BP
        MOV     BP,SP
        PUSHF
        PUSH    DS
        PUSH    SI
        PUSH    DI
        CLD                     ;up
        XOR     AX,AX
        MOV     CX,[BP+14]      ;size
        JCXZ    UNPCK9
        LES     DI,[BP+6];      ;ES:DI -> string
        LDS     SI,[BP+10];     ;DS:SI -> string
        PUSH    DI
;
UNPCK0: LODSB                    ;-- pcbord/qmail file corruption fix
        CMP     AL,30H
        JAE     @F
        CMP     AL,'#'           ;<<<
        JNE     UNPK2
        MOV     AL,'@'           ;<<<
@@:     CMP     AL,0B0H
        JA      UNPK2
        STOSB
UNPK2:  LOOP    UNPCK0
;                                ;------------
        MOV     AX,DI
        POP     DI
        SUB     AX,DI           ;total "clean" bytes
        LDS     SI,[BP+6]
        LES     DI,[BP+6]       ;works only on destination
        ADD     AX,7
        SHR     AX,3            ;number of loops
        MOV     CX,AX
        MOV     BX,7
        MUL     BX              ;number of bytes
        MOV     BX,AX           ;save on BX
UNPCK1: MOV     DH,[SI+7]       ;get saved bit
        SUB     DH,30h          ;convert to 0 - 7Fh
        RCL     DH,1
        LODSW
        SUB     AX,3030h        ;convert data too
        RCL     DH,1            ;bit -> CY
        RCL     AL,1            ;insert on byte
        RCL     DH,1            ;next byte
        RCL     AH,1
        STOSW                   ;save
        LODSW
        SUB     AX,3030h        ;next 2 bytes
        RCL     DH,1
        RCL     AL,1
        RCL     DH,1
        RCL     AH,1
        STOSW
        LODSW
        SUB     AX,3030h        ;bytes 5 & 6
        RCL     DH,1
        RCL     AL,1
        RCL     DH,1
        RCL     AH,1
        STOSW
        LODSW
        SUB     AL,30h          ;the last...
        RCL     DH,1
        RCL     AL,1
        STOSB
        LOOP    UNPCK1          ;repeat all
        MOV     AX,BX
UNPCK9: XOR     DX,DX           ;return total processed.
        POP     DI
        POP     SI
        POP     DS
        POPF
        MOV     SP,BP
        POP     BP
        RET
_Unpack ENDP
;---------------------------------------------------------------------
;       Assembly routines to convert from 8 to 4 bits and from 4 to 8
;---------------------------------------------------------------------
;       Log2 (LPSTR, UINT);
;
_Log2   PROC    FAR
        PUSH    BP
        MOV     BP,SP
        PUSHF
        PUSH    ES
        PUSH    DI
        CLD
        MOV     BX,[BP+10]      ;size to CX
        OR      BX,BX
        JZ      LOGC9
        LES     DI,[BP+6];      ;ES:DI -> in/out - in place conversion
LOGC1:  MOV     AL,ES:[DI]
        XOR     CH,CH           ;zero
        MOV     DL,CH           ;
        sub     al,80h          ;remove DC value (-128..127)
        OR      AL,AL
        JNS     @F              ;if positive, go
        NEG     AL              ;signal change
        MOV     DL,8            ;signal bit
@@:     AND     AL,7FH
        SHL     AL,1
        MOV     CL,7
@@:     SHL     AL,1
        JC      @F
        LOOP    @B
@@:     OR      CL,DL           ;insert signal
        MOV     AL,CL
;
        STOSB                   ;save
        DEC     BX              ;end test
        JNZ     LOGC1           ;no, more...
LOGC9:  POP     DI
        POP     ES
        POPF
        MOV     SP,BP
        POP     BP
        RET
_Log2   ENDP
;-------------------------------------------------------------------
;       Exp2 (LPSTR, UINT);
;
_LogExp PROC    FAR
        PUSH    BP
        MOV     BP,SP
        PUSHF
        PUSH    ES
        PUSH    DI
        CLD                     ;up
        XOR     AX,AX
        MOV     BX,[BP+10]      ;size
        OR      BX,BX
        JZ      LOGE9
        LES     DI,[BP+6];      ;ES:DI -> in/out
        XOR     CH,CH
LOGE1:  MOV     AL,ES:[DI]
        XOR     AH,AH
;
        MOV     CL,AL           ;copy
        AND     CL,7            ;signal mask
        STC
        RCL     AH,CL           ;exp
        TEST    AL,8
        JZ      @F              ;if positive
        NEG     AH
@@:     ADD     AH,80H          ;recover offset
        MOV     AL,AH
        STOSB                   ;save
        DEC     BX
        JNZ     LOGE1           ;loop
LOGE9:  POP     DI
        POP     ES
        POPF
        MOV     SP,BP
        POP     BP
        RET
_Exp2   ENDP
;========================================================================

OBS: The music appended to the end of messages is a .MID file only coded
into 7 bits.     The file header is the same, only the identifier "TALK"
change to "MIDI".

==========================================================================
                                            Carlos Pires, Rio, March 1993.
==========================================================================
NOTE: Unpack routine correction to avoid file corruption over some BBSs
      systems, the change of "@" by "#".
                                                                June, 1993.
==========================================================================
NOTE: Coding scheme "B" using 6 bits only to avoid some old fashioned
      BBS.  This solve the problem of bit 7 stripping.      September 1993.
==========================================================================
