[BACK]Return to dev.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / aucat

Diff for /src/usr.bin/aucat/Attic/dev.c between version 1.2 and 1.3

version 1.2, 2008/08/14 15:25:16 version 1.3, 2008/10/26 08:49:43
Line 17 
Line 17 
 #include <stdio.h>  #include <stdio.h>
 #include <stdlib.h>  #include <stdlib.h>
 #include <unistd.h>  #include <unistd.h>
 #include <signal.h>  
 #include <err.h>  
   
 #include "dev.h"  #include "dev.h"
 #include "abuf.h"  #include "abuf.h"
 #include "aproc.h"  #include "aproc.h"
 #include "file.h"  #include "pipe.h"
 #include "conf.h"  #include "conf.h"
   #include "safile.h"
   
 int quit_flag, pause_flag;  unsigned dev_bufsz, dev_round, dev_rate;
 unsigned dev_infr, dev_onfr;  unsigned dev_rate_div, dev_round_div;
 struct aparams dev_ipar, dev_opar;  struct aparams dev_ipar, dev_opar;
 struct aproc *dev_mix, *dev_sub, *dev_rec, *dev_play;  struct aproc *dev_mix, *dev_sub, *dev_rec, *dev_play;
 struct file  *dev_file;  struct file *dev_file;
 struct devops *devops = &devops_sun;  
   
 /*  /*
  * SIGINT handler, it raises the quit flag. If the flag is already set,   * supported rates
  * that means that the last SIGINT was not handled, because the process  
  * is blocked somewhere, so exit  
  */   */
 void  #define NRATES (sizeof(dev_rates) / sizeof(dev_rates[0]))
 sigint(int s)  unsigned dev_rates[] = {
 {            6400,   7200,   8000,   9600,  11025,  12000,
         if (quit_flag)           12800,  14400,  16000,  19200,  22050,  24000,
                 _exit(1);           25600,  28800,  32000,  38400,  44100,  48000,
         quit_flag = 1;           51200,  57600,  64000,  76800,  88200,  96000,
 }          102400, 115200, 128000, 153600, 176400, 192000
   };
   
 /*  /*
  * called when the user hits ctrl-z   * factors of supported rates
  */   */
 void  #define NPRIMES (sizeof(dev_primes) / sizeof(dev_primes[0]))
 sigtstp(int s)  unsigned dev_primes[] = {2, 3, 5, 7};
 {  
         pause_flag = 1;  
 }  
   
 /*  int
  * SIGCONT is send when resumed after SIGTSTP or SIGSTOP. If the pause  dev_setrate(unsigned rate)
  * flag is not set, that means that the process was not suspended by  
  * dev_suspend(), which means that we lost the sync; since we cannot  
  * resync, just exit  
  */  
 void  
 sigcont(int s)  
 {  {
         static char msg[] = "can't resume afer SIGSTOP, terminating...\n";          unsigned i, r, p;
   
         if (!pause_flag) {  
                 write(STDERR_FILENO, msg, sizeof(msg) - 1);  
                 _exit(1);  
         }  
 }  
   
 /*          r = 1000 * rate;
  * suicide with SIGTSTP (tty stop) as if the user had hit ctrl-z          for (i = 0; i < NRATES; i++) {
  */                  if (i == NRATES) {
 void                          fprintf(stderr, "dev_setrate: %u, unsupported\n", rate);
 dev_suspend(void)                          return 0;
 {                  }
         struct sigaction sa;                  if (r > 996 * dev_rates[i] &&
                       r < 1004 * dev_rates[i]) {
         sigfillset(&sa.sa_mask);                          dev_rate = dev_rates[i];
         sa.sa_flags = SA_RESTART;  
         sa.sa_handler = SIG_DFL;  
         if (sigaction(SIGTSTP, &sa, NULL) < 0)  
                 err(1, "sigaction");  
         DPRINTF("suspended by tty\n");  
         kill(getpid(), SIGTSTP);  
         pause_flag = 0;  
         sa.sa_handler = sigtstp;  
         if (sigaction(SIGTSTP, &sa, NULL) < 0)  
                 err(1, "sigaction");  
         DPRINTF("resumed after suspend\n");  
 }  
   
 /*  
  * fill playback buffer, so when device is started there  
  * are samples to play  
  */  
 void  
 dev_fill(void)  
 {  
         struct abuf *buf;  
   
   
         /*  
          * if there are no inputs, zero fill the mixer  
          */  
         if (dev_mix && LIST_EMPTY(&dev_mix->ibuflist))  
                 mix_pushzero(dev_mix);  
         DPRINTF("filling play buffers...\n");  
         for (;;) {  
                 if (!dev_file->wproc) {  
                         DPRINTF("fill: no writer\n");  
                         break;                          break;
                 }                  }
                 if (dev_file->events & POLLOUT) {          }
                         /*  
                          * kernel buffers are full, but continue          dev_rate_div = dev_rate;
                          * until the play buffer is full too.          dev_round_div = dev_round;
                          */          for (i = 0; i < NPRIMES; i++) {
                         buf = LIST_FIRST(&dev_file->wproc->ibuflist);                  p = dev_primes[i];
                         if (!ABUF_WOK(buf))                  while (dev_rate_div % p == 0 && dev_round_div % p == 0) {
                                 break;          /* buffer full */                          dev_rate_div /= p;
                         if (!buf->wproc)                          dev_round_div /= p;
                                 break;          /* will never be filled */  
                 }                  }
                 if (!file_poll())  
                         break;  
                 if (pause_flag)  
                         dev_suspend();  
         }          }
           return 1;
 }  }
   
 /*  
  * flush recorded samples once the device is stopped so  
  * they aren't lost  
  */  
 void  void
 dev_flush(void)  dev_roundrate(unsigned *newrate, unsigned *newround)
 {  {
         struct abuf *buf;          *newrate += dev_rate_div - 1;
           *newrate -= *newrate % dev_rate_div;
         DPRINTF("flushing record buffers...\n");          *newround = *newrate * dev_round_div / dev_rate_div;
         for (;;) {  
                 if (!dev_file->rproc) {  
                         DPRINTF("flush: no more reader\n");  
                         break;  
                 }  
                 if (dev_file->events & POLLIN) {  
                         /*  
                          * we drained kernel buffers, but continue  
                          * until the record buffer is empty.  
                          */  
                         buf = LIST_FIRST(&dev_file->rproc->obuflist);  
                         if (!ABUF_ROK(buf))  
                                 break;          /* buffer empty */  
                         if (!buf->rproc)  
                                 break;          /* will never be drained */  
                 }  
                 if (!file_poll())  
                         break;  
                 if (pause_flag)  
                         dev_suspend();  
         }  
 }  }
   
   
 /*  /*
  * open the device with the given hardware parameters and create a mixer   * open the device with the given hardware parameters and create a mixer
  * and a multiplexer connected to it with all necessary conversions   * and a multiplexer connected to it with all necessary conversions
  * setup   * setup
  */   */
 void  void
 dev_init(char *devpath, struct aparams *dipar, struct aparams *dopar)  dev_init(char *devpath,
       struct aparams *dipar, struct aparams *dopar, unsigned bufsz)
 {  {
         int fd;  
         struct sigaction sa;  
         unsigned infr, onfr;  
         struct aparams ipar, opar;          struct aparams ipar, opar;
         struct aproc *conv;          struct aproc *conv;
         struct abuf *buf;          struct abuf *buf;
           unsigned nfr, ibufsz, obufsz;
   
         quit_flag = 0;          /*
         pause_flag = 0;           * use 1/4 of the total buffer for the device
            */
         sigfillset(&sa.sa_mask);          dev_bufsz = (bufsz + 3) / 4;
         sa.sa_flags = SA_RESTART;          dev_file = (struct file *)safile_new(&safile_ops, devpath,
         sa.sa_handler = sigint;              dipar, dopar, &dev_bufsz, &dev_round);
         if (sigaction(SIGINT, &sa, NULL) < 0)          if (!dev_file)
                 err(1, "sigaction");  
         sa.sa_handler = sigtstp;  
         if (sigaction(SIGTSTP, &sa, NULL) < 0)  
                 err(1, "sigaction");  
         sa.sa_handler = sigcont;  
         if (sigaction(SIGCONT, &sa, NULL) < 0)  
                 err(1, "sigaction");  
   
         fd = devops->open(devpath, dipar, dopar, &infr, &onfr);  
         if (fd < 0)  
                 exit(1);                  exit(1);
         dev_file = file_new(fd, devpath);          if (!dev_setrate(dipar ? dipar->rate : dopar->rate))
                   exit(1);
           if (dipar) {
                   dipar->rate = dev_rate;
                   if (debug_level > 0) {
                           DPRINTF("dev_init: dipar: ");
                           aparams_print(dipar);
                           DPRINTF("\n");
                   }
           }
           if (dopar) {
                   dopar->rate = dev_rate;
                   if (debug_level > 0) {
                           DPRINTF("dev_init: dopar: ");
                           aparams_print(dopar);
                           DPRINTF("\n");
                   }
           }
           nfr = ibufsz = obufsz = dev_bufsz;
   
         /*          /*
          * create record chain           * create record chain: use 1/4 for the file i/o buffers
          */           */
         if (dipar) {          if (dipar) {
                 aparams_init(&ipar, dipar->cmin, dipar->cmax, dipar->rate);                  aparams_init(&ipar, dipar->cmin, dipar->cmax, dipar->rate);
                 infr *= DEFAULT_NBLK;  
   
                 /*                  /*
                  * create the read end                   * create the read end
                  */                   */
                 dev_rec = rpipe_new(dev_file);                  dev_rec = rpipe_new(dev_file);
                 buf = abuf_new(infr, aparams_bpf(dipar));                  buf = abuf_new(nfr, aparams_bpf(dipar));
                 aproc_setout(dev_rec, buf);                  aproc_setout(dev_rec, buf);
                   ibufsz += nfr;
   
                 /*                  /*
                  * append a converter, if needed                   * append a converter, if needed
Line 227 
Line 153 
                         }                          }
                         conv = conv_new("subconv", dipar, &ipar);                          conv = conv_new("subconv", dipar, &ipar);
                         aproc_setin(conv, buf);                          aproc_setin(conv, buf);
                         buf = abuf_new(infr, aparams_bpf(&ipar));                          buf = abuf_new(nfr, aparams_bpf(&ipar));
                         aproc_setout(conv, buf);                          aproc_setout(conv, buf);
                           ibufsz += nfr;
                 }                  }
                 dev_ipar = ipar;                  dev_ipar = ipar;
                 dev_infr = infr;  
   
                 /*                  /*
                  * append a "sub" to which clients will connect                   * append a "sub" to which clients will connect
                  */                   */
                 dev_sub = sub_new();                  dev_sub = sub_new("sub", nfr);
                 aproc_setin(dev_sub, buf);                  aproc_setin(dev_sub, buf);
         } else {          } else {
                 dev_rec = NULL;                  dev_rec = NULL;
Line 248 
Line 174 
          */           */
         if (dopar) {          if (dopar) {
                 aparams_init(&opar, dopar->cmin, dopar->cmax, dopar->rate);                  aparams_init(&opar, dopar->cmin, dopar->cmax, dopar->rate);
                 onfr *= DEFAULT_NBLK;  
   
                 /*                  /*
                  * create the write end                   * create the write end
                  */                   */
                 dev_play = wpipe_new(dev_file);                  dev_play = wpipe_new(dev_file);
                 buf = abuf_new(onfr, aparams_bpf(dopar));                  buf = abuf_new(nfr, aparams_bpf(dopar));
                 aproc_setin(dev_play, buf);                  aproc_setin(dev_play, buf);
                   obufsz += nfr;
   
                 /*                  /*
                  * append a converter, if needed                   * append a converter, if needed
                  */                   */
Line 268 
Line 193 
                         }                          }
                         conv = conv_new("mixconv", &opar, dopar);                          conv = conv_new("mixconv", &opar, dopar);
                         aproc_setout(conv, buf);                          aproc_setout(conv, buf);
                         buf = abuf_new(onfr, aparams_bpf(&opar));                          buf = abuf_new(nfr, aparams_bpf(&opar));
                         aproc_setin(conv, buf);                          aproc_setin(conv, buf);
                         *dopar = opar;                          obufsz += nfr;
                 }                  }
                 dev_opar = opar;                  dev_opar = opar;
                 dev_onfr = onfr;  
   
                 /*                  /*
                  * append a "mix" to which clients will connect                   * append a "mix" to which clients will connect
                  */                   */
                 dev_mix = mix_new();                  dev_mix = mix_new("mix", nfr);
                 aproc_setout(dev_mix, buf);                  aproc_setout(dev_mix, buf);
         } else {          } else {
                 dev_play = NULL;                  dev_play = NULL;
                 dev_mix = NULL;                  dev_mix = NULL;
         }          }
           dev_bufsz = (dopar) ? obufsz : ibufsz;
           DPRINTF("dev_init: using %u fpb\n", dev_bufsz);
           dev_start();
 }  }
   
 /*  /*
Line 293 
Line 220 
 void  void
 dev_done(void)  dev_done(void)
 {  {
         struct sigaction sa;  
         struct file *f;          struct file *f;
   
         /*          if (dev_mix) {
          * generate EOF on all inputs (including device), so once                  /*
          * buffers are drained, everything will be cleaned                   * generate EOF on all inputs (but not the device), and
          */                   * put the mixer in ``autoquit'' state, so once buffers
         LIST_FOREACH(f, &file_list, entry) {                   * are drained the mixer will terminate and shutdown the
                 if (f->rproc)                   * write-end of the device
                         file_eof(f);                   *
                    * NOTE: since file_eof() can destroy the file and
                    * reorder the file_list, we have to restart the loop
                    * after each call to file_eof()
                    */
           restart:
                   LIST_FOREACH(f, &file_list, entry) {
                           if (f != dev_file && f->rproc) {
                                   file_eof(f);
                                   goto restart;
                           }
                   }
                   if (dev_mix)
                           dev_mix->u.mix.flags |= MIX_AUTOQUIT;
   
                   /*
                    * wait play chain to terminate
                    */
                   while (dev_file->wproc != NULL) {
                           if (!file_poll())
                                   break;
                   }
                   dev_mix = 0;
         }          }
         /*          if (dev_sub) {
          * destroy automatically mixe instead                  /*
          * of generating silence                   * same as above, but for the record chain: generate eof
          */                   * on the read-end of the device and wait record buffers
         if (dev_mix)                   * to desappear.  We must stop the device first, because
                 dev_mix->u.mix.flags |= MIX_AUTOQUIT;                   * play-end will underrun (and xrun correction code will
         if (dev_sub)                   * insert silence on the record-end of the device)
                 dev_sub->u.sub.flags |= SUB_AUTOQUIT;                   */
         /*                  dev_stop();
          * drain buffers of terminated inputs.                  file_eof(dev_file);
          */                  if (dev_sub)
         for (;;) {                          dev_sub->u.sub.flags |= SUB_AUTOQUIT;
                 if (!file_poll())                  for (;;) {
                         break;                          if (!file_poll())
                                   break;
                   }
                   dev_sub = NULL;
         }          }
         devops->close(dev_file->fd);  
   
         sigfillset(&sa.sa_mask);  
         sa.sa_flags = SA_RESTART;  
         sa.sa_handler = SIG_DFL;  
         if (sigaction(SIGINT, &sa, NULL) < 0)  
                 err(1, "sigaction");  
         if (sigaction(SIGTSTP, &sa, NULL) < 0)  
                 err(1, "sigaction");  
         if (sigaction(SIGCONT, &sa, NULL) < 0)  
                 err(1, "sigaction");  
 }  }
   
 /*  /*
Line 338 
Line 278 
 void  void
 dev_start(void)  dev_start(void)
 {  {
         dev_fill();  
         if (dev_mix)          if (dev_mix)
                 dev_mix->u.mix.flags |= MIX_DROP;                  dev_mix->u.mix.flags |= MIX_DROP;
         if (dev_sub)          if (dev_sub)
                 dev_sub->u.sub.flags |= SUB_DROP;                  dev_sub->u.sub.flags |= SUB_DROP;
         devops->start(dev_file->fd);          dev_file->ops->start(dev_file);
 }  }
   
 /*  /*
Line 352 
Line 291 
 void  void
 dev_stop(void)  dev_stop(void)
 {  {
         devops->stop(dev_file->fd);          dev_file->ops->stop(dev_file);
         if (dev_mix)          if (dev_mix)
                 dev_mix->u.mix.flags &= ~MIX_DROP;                  dev_mix->u.mix.flags &= ~MIX_DROP;
         if (dev_sub)          if (dev_sub)
                 dev_sub->u.sub.flags &= ~SUB_DROP;                  dev_sub->u.sub.flags &= ~SUB_DROP;
         dev_flush();  
 }  }
   
 /*  /*
  * loop until there's either input or output to process   * sync play buffer to rec buffer (for instance when one of
    * them underruns/overruns)
  */   */
 void  void
 dev_run(int autoquit)  dev_sync(struct abuf *ibuf, struct abuf *obuf)
 {  {
         while (!quit_flag) {          struct abuf *pbuf, *rbuf;
                 if ((!dev_mix || LIST_EMPTY(&dev_mix->ibuflist)) &&          int delta;
                     (!dev_sub || LIST_EMPTY(&dev_sub->obuflist)) && autoquit)  
           if (!dev_mix || !dev_sub)
                   return;
           pbuf = LIST_FIRST(&dev_mix->obuflist);
           if (!pbuf)
                   return;
           rbuf = LIST_FIRST(&dev_sub->ibuflist);
           if (!rbuf)
                   return;
           for (;;) {
                   if (!ibuf || !ibuf->rproc) {
                           DPRINTF("dev_sync: reader desappeared\n");
                           return;
                   }
                   if (ibuf->rproc == dev_mix)
                         break;                          break;
                 if (!file_poll())                  ibuf = LIST_FIRST(&ibuf->rproc->obuflist);
                         break;          }
                 if (pause_flag) {          for (;;) {
                         devops->stop(dev_file->fd);                  if (!obuf || !obuf->wproc) {
                         dev_flush();                          DPRINTF("dev_sync: writer desappeared\n");
                         dev_suspend();                          return;
                         dev_fill();  
                         devops->start(dev_file->fd);  
                 }                  }
                   if (obuf->wproc == dev_sub)
                           break;
                   obuf = LIST_FIRST(&obuf->wproc->ibuflist);
         }          }
   
           /*
            * calculate delta, the number of frames the play chain is ahead
            * of the record chain. It's necessary to schedule silences (or
            * drops) in order to start playback and record in sync.
            */
           delta =
               rbuf->bpf * (pbuf->abspos + pbuf->used) -
               pbuf->bpf *  rbuf->abspos;
           delta /= pbuf->bpf * rbuf->bpf;
           DPRINTF("dev_sync: delta = %d, ppos = %u, pused = %u, rpos = %u\n",
               delta, pbuf->abspos, pbuf->used, rbuf->abspos);
   
           if (delta > 0) {
                   /*
                    * if the play chain is ahead (most cases) drop some of
                    * the recorded input, to get both in sync
                    */
                   obuf->drop += delta * obuf->bpf;
                   abuf_ipos(obuf, -delta);
           } else if (delta < 0) {
                   /*
                    * if record chain is ahead (should never happen,
                    * right?) then insert silence to play
                    */
                   ibuf->silence += -delta * ibuf->bpf;
                   abuf_opos(ibuf, delta);
           } else
                   DPRINTF("dev_sync: nothing to do\n");
 }  }
   
 /*  /*
Line 393 
Line 376 
     struct abuf *ibuf, struct aparams *ipar, unsigned underrun,      struct abuf *ibuf, struct aparams *ipar, unsigned underrun,
     struct abuf *obuf, struct aparams *opar, unsigned overrun)      struct abuf *obuf, struct aparams *opar, unsigned overrun)
 {  {
         int delta;  
         struct abuf *pbuf = NULL, *rbuf = NULL;          struct abuf *pbuf = NULL, *rbuf = NULL;
         struct aproc *conv;          struct aproc *conv;
           unsigned nfr;
   
         if (ibuf) {          if (ibuf) {
                 pbuf = LIST_FIRST(&dev_mix->obuflist);                  pbuf = LIST_FIRST(&dev_mix->obuflist);
Line 405 
Line 388 
                                 aparams_print2(ipar, &dev_opar);                                  aparams_print2(ipar, &dev_opar);
                                 fprintf(stderr, "\n");                                  fprintf(stderr, "\n");
                         }                          }
                           nfr = (dev_bufsz + 3) / 4 + dev_round - 1;
                           nfr -= nfr % dev_round;
                         conv = conv_new(name, ipar, &dev_opar);                          conv = conv_new(name, ipar, &dev_opar);
                         aproc_setin(conv, ibuf);                          aproc_setin(conv, ibuf);
                         ibuf = abuf_new(dev_onfr, aparams_bpf(&dev_opar));                          ibuf = abuf_new(nfr, aparams_bpf(&dev_opar));
                         aproc_setout(conv, ibuf);                          aproc_setout(conv, ibuf);
                           /* XXX: call abuf_fill() here ? */
                 }                  }
                 aproc_setin(dev_mix, ibuf);                  aproc_setin(dev_mix, ibuf);
                   abuf_opos(ibuf, -dev_mix->u.mix.lat);
                 ibuf->xrun = underrun;                  ibuf->xrun = underrun;
                 mix_setmaster(dev_mix);  
         }          }
         if (obuf) {          if (obuf) {
                 rbuf = LIST_FIRST(&dev_sub->ibuflist);                  rbuf = LIST_FIRST(&dev_sub->ibuflist);
Line 422 
Line 408 
                                 aparams_print2(&dev_ipar, opar);                                  aparams_print2(&dev_ipar, opar);
                                 fprintf(stderr, "\n");                                  fprintf(stderr, "\n");
                         }                          }
                           nfr = (dev_bufsz + 3) / 4 + dev_round - 1;
                           nfr -= nfr % dev_round;
                         conv = conv_new(name, &dev_ipar, opar);                          conv = conv_new(name, &dev_ipar, opar);
                         aproc_setout(conv, obuf);                          aproc_setout(conv, obuf);
                         obuf = abuf_new(dev_infr, aparams_bpf(&dev_ipar));                          obuf = abuf_new(nfr, aparams_bpf(&dev_ipar));
                         aproc_setin(conv, obuf);                          aproc_setin(conv, obuf);
                 }                  }
                 aproc_setout(dev_sub, obuf);                  aproc_setout(dev_sub, obuf);
                   abuf_ipos(obuf, -dev_sub->u.sub.lat);
                 obuf->xrun = overrun;                  obuf->xrun = overrun;
         }          }
   
         /*          /*
          * calculate delta, the number of frames the play chain is ahead           * sync play to record
          * of the record chain. It's necessary to schedule silences (or  
          * drops) in order to start playback and record in sync.  
          */           */
         if (ibuf && obuf) {          if (ibuf && obuf) {
                 delta =                  ibuf->duplex = obuf;
                     rbuf->bpf * (pbuf->abspos + pbuf->used) -                  obuf->duplex = ibuf;
                     pbuf->bpf *  rbuf->abspos;                  dev_sync(ibuf, obuf);
                 delta /= pbuf->bpf * rbuf->bpf;  
                 DPRINTF("dev_attach: ppos = %u, pused = %u, rpos = %u\n",  
                     pbuf->abspos, pbuf->used, rbuf->abspos);  
         } else  
                 delta = 0;  
         DPRINTF("dev_attach: delta = %u\n", delta);  
   
         if (delta > 0) {  
                 /*  
                  * if the play chain is ahead (most cases) drop some of  
                  * the recorded input, to get both in sync  
                  */  
                 obuf->drop += delta * obuf->bpf;  
         } else if (delta < 0) {  
                 /*  
                  * if record chain is ahead (should never happen,  
                  * right?) then insert silence to play  
                  */  
                 ibuf->silence += -delta * ibuf->bpf;  
         }  
         if (ibuf && (dev_mix->u.mix.flags & MIX_DROP)) {  
                 /*  
                  * fill the play buffer with silence to avoid underruns,  
                  * drop samples on the input to keep play/record in sync  
                  * after the silence insertion  
                  */  
                 ibuf->silence += dev_onfr * ibuf->bpf;  
                 if (obuf)  
                         obuf->drop += dev_onfr * obuf->bpf;  
                 /*  
                  * force data to propagate  
                  */  
                 abuf_run(ibuf);  
                 DPRINTF("dev_attach: ibuf: used = %u, silence = %u\n",  
                     ibuf->used, ibuf->silence);  
         }  
         if (obuf && (dev_sub->u.mix.flags & SUB_DROP)) {  
                 abuf_run(obuf);  
                 DPRINTF("dev_attach: ibuf: used = %u, drop = %u\n",  
                     obuf->used, obuf->drop);  
         }          }
 }  }

Legend:
Removed from v.1.2  
changed lines
  Added in v.1.3