コード例 #1
0
ファイル: streamer.c プロジェクト: tlsalmin/vlbi-streamer
int print_midstats(struct schedule *sched, struct stats *old_stats)
{
  TIMERTYPE temptime;
  float timedif = 0;
  struct listed_entity *le = sched->br.busylist;
  struct stats tempstats;
  float total_mbits = 0;
  GETTIME(temptime);
  timedif = floatdiff(&temptime, &sched->lasttick);
  LOG("Time:\t%lu\n", GETSECONDS(temptime));
  while (le != NULL)
    {
      struct scheduled_event *ev = (struct scheduled_event *)le->entity;
      if (ev->opt->get_stats != NULL)
        {
          memset(&tempstats, 0, sizeof(struct stats));
          ev->opt->get_stats((void *)ev->opt, (void *)&tempstats);
          neg_stats(&tempstats, ev->stats);
          total_mbits = BYTES_TO_MBITSPS(tempstats.total_bytes);
          LOG("Event:\t%s\t", ev->opt->filename);
          LOG("Network:\t%5.0fMb/s\tDropped %lu\tIncomplete %lu",
              total_mbits / timedif, tempstats.dropped, tempstats.incomplete);
          if (tempstats.progress != -1)
            {
              LOG("\tProgress %02d%%", tempstats.progress);
            }
          LOG("\n");
          add_stats(ev->stats, &tempstats);
        }
      le = le->child;
    }
  memset(&tempstats, 0, sizeof(struct stats));
  oper_to_all(sched->default_opt->diskbranch, BRANCHOP_GETSTATS,
              (void *)&tempstats);
  neg_stats(&tempstats, old_stats);
  total_mbits = BYTES_TO_MBITSPS(tempstats.total_written);
  LOG("HD-Speed:\t%5.0fMb/s\n", total_mbits / timedif);
  add_stats(old_stats, &tempstats);

  LOG("Ringbuffers: ");
  print_br_stats(sched->default_opt->membranch);
  LOG("Recpoints: ");
  print_br_stats(sched->default_opt->diskbranch);

  COPYTIME(temptime, sched->lasttick);
  LOG("----------------------------------------\n");
  return 0;
}
コード例 #2
0
ファイル: streamer.c プロジェクト: tlsalmin/vlbi-streamer
int parse_options(int argc, char **argv, struct opt_s *opt)
{
  int ret;
  while ((ret = getopt(argc, argv, "d:i:t:s:n:m:w:p:q:ur:a:vVI:A:W:xc:hf:"))
         != -1)
    {
      switch (ret)
        {
        case 'i':
          opt->address_to_bind_to = strdup(optarg);
          break;
        case 'c':
          opt->cfgfile = (char *)malloc(sizeof(char) * FILENAME_MAX);
          //CHECK_ERR_NONNULL(opt->cfgfile, "Cfgfile malloc");
          LOG
            ("Path for cfgfile specified. All command line options before this argument might be ignored\n");
          ret = read_full_cfg(opt);
          if (ret != 0)
            {
              E("Error parsing cfg file. Exiting");
              free(opt->cfgfile);
              return -1;
            }
          break;
        case 'v':
          opt->optbits |= VERBOSE;
          break;
        case 'f':
          LOG("Logging to %s\n", optarg);
          fflush(file_out);
          file_out = fopen(optarg, "a");
          if (!file_out)
            {
              E("Failed to open log file %s: %s", optarg, strerror(errno));
              return -1;
            }
          file_err = file_out;
          break;
        case 'h':
          usage(argv[0]);
          return -1;
          break;
        case 'd':
          opt->n_drives = atoi(optarg);
          break;
#if(DAEMON)
        case 'e':
          GETSECONDS(opt->starting_time) = atoi(optarg);
          break;
#endif
        case 'I':
          opt->minmem = atoi(optarg);
          break;
        case 'x':
          opt->optbits |= USE_RX_RING;
          break;
        case 'W':
          opt->do_w_stuff_every = atoi(optarg) * MEG;
          break;
        case 'A':
          opt->maxmem = atoi(optarg);
          break;
        case 'V':
          opt->optbits |= MOUNTPOINT_VERBOSE;
          break;
        case 't':
          opt->optbits &= ~LOCKER_CAPTURE;
          if (!strcmp(optarg, "fanout"))
            {
              //opt->capture_type = CAPTURE_W_FANOUT;
              opt->optbits |= CAPTURE_W_FANOUT;
            }
          else if (!strcmp(optarg, "udpstream"))
            {
              //opt->capture_type = CAPTURE_W_UDPSTREAM;
              opt->optbits |= CAPTURE_W_UDPSTREAM;
            }
          /*
             else if (!strcmp(optarg, "sendfile")){
             //opt->capture_type = CAPTURE_W_SPLICER;
             opt->optbits |= CAPTURE_W_SPLICER;
             }
           */
          else if (!strcmp(optarg, "dummy"))
            {
              //opt->capture_type = CAPTURE_W_SPLICER;
              opt->optbits |= CAPTURE_W_DUMMY;
            }
          else
            {
              LOGERR("Unknown packet capture type [%s]\n", optarg);
              usage(argv[0]);
              return -1;
            }
          break;
          /* Fanout choosing removed and set to default LB since
           * Implementation not that feasible anyway
           case 'a':
           opt->optbits &= ~LOCKER_FANOUT;
           if (!strcmp(optarg, "hash")){
           //opt->fanout_type = PACKET_FANOUT_HASH;
           opt->optbits |= PACKET_FANOUT_HASH;
           }
           else if (!strcmp(optarg, "lb")){
           //opt->fanout_type = PACKET_FANOUT_LB;
           opt->optbits |= PACKET_FANOUT_LB;
           }
           else {
           LOGERR("Unknown fanout type [%s]\n", optarg);
           usage(argv[0]);
           exit(1);
           }
           break;
           */
        case 'a':
#ifdef HAVE_RATELIMITER
          //opt->optbits |= WAIT_BETWEEN;
          opt->wait_nanoseconds = atoi(optarg) * 1000;
          ZEROTIME(opt->wait_last_sent);
#else
          LOGERR("STREAMER: Rate limiter not compiled\n");
#endif
          break;
        case 'r':
          opt->rate = atoi(optarg);
          break;
        case 's':
          opt->port = atoi(optarg);
          break;
        case 'p':
          opt->packet_size = atoi(optarg);
          break;
        case 'u':
#if(HAVE_HUGEPAGES)
          opt->optbits |= USE_HUGEPAGE;
#endif
          break;
        case 'n':
          /* Commandeering this option */
          opt->hostname = (char *)malloc(sizeof(char) * IP_LENGTH);
          if (strcpy(opt->hostname, optarg) == NULL)
            {
              E("strcpy hostname");
              return -1;
            }
          //opt->n_threads = atoi(optarg);
          break;
        case 'q':
          opt->optbits &= ~LOCKER_DATATYPE;
          if (!strcmp(optarg, "vdif"))
            {
              D("Datatype as VDIF");
              opt->optbits |= DATATYPE_VDIF;
            }
          else if (!strcmp(optarg, "mark5b"))
            {
              D("Datatype as Mark5");
              opt->optbits |= DATATYPE_MARK5B;
            }
          else if (!strcmp(optarg, "udpmon"))
            {
              D("Datatype as UDPMON");
              opt->optbits |= DATATYPE_UDPMON;
            }
          else if (!strcmp(optarg, "none"))
            {
              D("Datatype as none");
              opt->optbits |= DATATYPE_UNKNOWN;
            }
          else
            {
              E("Unknown datatype %s", optarg);
              opt->optbits |= DATATYPE_UNKNOWN;
            }
          break;
        case 'm':
          if (!strcmp(optarg, "r"))
            {
              opt->optbits &= ~READMODE;
              //opt->read = 0;
            }
          else if (!strcmp(optarg, "s"))
            {
              //opt->read = 1;
              opt->optbits |= READMODE;
            }
          else
            {
              LOGERR("Unknown mode type [%s]\n", optarg);
              usage(argv[0]);
              exit(1);
            }
          break;
        case 'w':
          opt->optbits &= ~LOCKER_REC;
          if (!strcmp(optarg, "def"))
            {
              /*
                 opt->rec_type = REC_DEF;
                 opt->async = 0;
               */
              opt->optbits |= REC_DEF;
              opt->optbits &= ~ASYNC_WRITE;
            }
#ifdef HAVE_LIBAIO
          else if (!strcmp(optarg, "aio"))
            {
              /*
                 opt->rec_type = REC_AIO;
                 opt->async = 1;
               */
              opt->optbits |= REC_AIO | ASYNC_WRITE;
            }
#endif
          else if (!strcmp(optarg, "splice"))
            {
              /*
                 opt->rec_type = REC_SPLICER;
                 opt->async = 0;
               */
              opt->optbits |= REC_SPLICER;
              opt->optbits &= ~ASYNC_WRITE;
            }
          else if (!strcmp(optarg, "dummy"))
            {
              /*
                 opt->rec_type = REC_DUMMY;
                 opt->buf_type = WRITER_DUMMY;
               */
              opt->optbits &= ~LOCKER_WRITER;
              opt->optbits |= REC_DUMMY | WRITER_DUMMY;
              opt->optbits &= ~ASYNC_WRITE;
            }
          else
            {
              LOGERR("Unknown mode type [%s]\n", optarg);
              usage(argv[0]);
              //exit(1);
              return -1;
            }
          break;
        default:
          usage(argv[0]);
          exit(1);
        }
    }
#if(!DAEMON)
  if (argc - optind != 2)
    {
      usage(argv[0]);
      exit(1);
    }
#endif
  argv += optind;
  //argc -=optind;

  /* TODO: Enable giving a custom cfg-file on invocation */
#if(!DAEMON)
  if (opt->cfgfile != NULL)
    {
    }
#endif //DAEMON

  /* If we're using rx-ring, then set the packet size to +TPACKET_HDRLEN */
  /*
     if(opt->optbits & USE_RX_RING)
     opt->packet_size += TPACKET_HDRLEN;
   */

  /* If n_threads isn't set, set it to n_drives +2      */
  /* Not used. Maxmem limits this instead               */
  /*
     if(opt->n_threads == 0)
     opt->n_threads = opt->n_drives +2;
   */

  if (opt->optbits & GET_A_FILENAME_AS_ARG)
    {
      opt->filename = (char *)malloc(sizeof(char) * FILENAME_MAX);
      CHECK_ERR_NONNULL(opt->filename, "filename malloc");
      if (strcpy(opt->filename, argv[0]) == NULL)
        {
          E("strcpy filename");
          return -1;
        }
      //opt->filename = argv[0];
    }
  //opt->points = (struct rec_point *)calloc(opt->n_drives, sizeof(struct rec_point));
#if(!DAEMON)
  if (opt->optbits & READMODE && !(opt->optbits & CAPTURE_W_DISK2FILE))
    {
      opt->hostname = (char *)malloc(sizeof(char) * IP_LENGTH);
      if (strcpy(opt->hostname, argv[1]) == NULL)
        {
          E("strcpy hostname");
          return -1;
        }
      //opt->hostname = argv[1];
    }
  else
    opt->time = atoi(argv[1]);
#endif

  struct rlimit rl;
  /* Query max size */
  /* TODO: Doesn't work properly althought mem seems to be unlimited */
  ret = getrlimit(RLIMIT_DATA, &rl);
  if (ret < 0)
    {
      LOGERR("Failed to get rlimit of memory\n");
      exit(1);
    }
#if(DEBUG_OUTPUT)
  LOG("STREAMER: Queried max mem size %ld \n", rl.rlim_cur);
#endif
  /* Check for memory limit                                             */
  //unsigned long minmem = MIN_MEM_GIG*GIG;
  if (opt->minmem > rl.rlim_cur && rl.rlim_cur != RLIM_INFINITY)
    {
#if(DEBUG_OUTPUT)
      LOG("STREAMER: Limiting memory to %lu\n", rl.rlim_cur);
#endif
      opt->minmem = rl.rlim_cur;
    }
  return 0;
}
コード例 #3
0
ファイル: streamer.c プロジェクト: tlsalmin/vlbi-streamer
int main(int argc, char **argv)
#endif
{
  int err = 0;
  pthread_t streamer_pthread;

#if(DAEMON)
  struct opt_s *opt = (struct opt_s *)opti;
  D("Starting thread from daemon");
#else
  LOG("Running in non-daemon mode\n");
  struct opt_s *opt = malloc(sizeof(struct opt_s));
  CHECK_ERR_NONNULL(opt, "opt malloc");
  LOG("STREAMER: Reading parameters\n");
  clear_and_default(opt, 1);
  err = parse_options(argc, argv, opt);
  if (err != 0)
    STREAMER_ERROR_EXIT;

  err = init_branches(opt);
  CHECK_ERR("init branches");
  err = init_recp(opt);
  CHECK_ERR("init recpoints");

#ifdef HAVE_LIBCONFIG_H
  err = init_cfg(opt);
  //TODO: cfg destruction
  if (err != 0)
    {
      E("Error in cfg init");
      STREAMER_ERROR_EXIT;
    }
#endif //HAVE_LIBCONFIG_H

  err = init_rbufs(opt);
  CHECK_ERR("init rbufs");
#endif //DAEMON
  /* Check and set cfgs at this point */
  //return -1;

  /* If we're sending stuff, check all the diskbranch members for the files they have   */
  /* Also updates the fileholders list to point the owners of files to correct drives   */
#ifdef HAVE_LIBCONFIG_H
  if (opt->optbits & READMODE)
    {
      oper_to_all(opt->diskbranch, BRANCHOP_CHECK_FILES, (void *)opt);
      LOG
        ("For recording %s: %lu files were found out of %lu total. file index shows %ld files\n",
         opt->filename, opt->cumul_found, opt->cumul, afi_get_n_files(opt->fi));
    }
#endif //HAVE_LIBCONFIG_H

  /* Handle hostname etc */
  /* TODO: Whats the best way that accepts any format? */

  err = prep_streamer(opt);
  if (err != 0)
    {
      STREAMER_ERROR_EXIT;
    }

  if (opt->optbits & READMODE)
    LOG("STREAMER: In main, starting sending thread \n");
  else
    LOG("STREAMER: In main, starting receiver thread \n");

#ifdef HAVE_LRT
  /*  TCP streams start counting from accept and others from here       */
  if (!(opt->optbits & (CAPTURE_W_TCPSPLICE | CAPTURE_W_TCPSTREAM)))
    GETTIME(opt->starting_time);

  set_status_for_opt(opt, STATUS_RUNNING);
  err =
    pthread_create(&streamer_pthread, NULL, opt->streamer_ent->start,
                   (void *)opt->streamer_ent);

  if (err != 0)
    {
      printf("ERROR; return code from pthread_create() is %d\n", err);
      STREAMER_ERROR_EXIT;
    }

  /* Other thread spawned so we can minimize our priority       */
  minimize_priority();

#ifdef TUNE_AFFINITY
  /* Caused some weird ass bugs and crashes so not used anymore NEVER   */
  /* Put the capture on the first core                                  */
  CPU_SET(0, &(opt->cpuset));
  err = pthread_setaffinity_np(streamer_pthread, sizeof(cpu_set_t), &cpuset);
  if (err != 0)
    {
      E("Error: setting affinity: %d", err);
    }
  CPU_ZERO(&cpuset);
#endif

#endif
  {
    /* READMODE shuts itself down so we just go to pthread_join                 */
    /* Check also that last_packet is 0. Else the thread should shut itself     */
    /* down                                                                     */
    if (!(opt->optbits & READMODE) && opt->last_packet == 0 &&
        !(opt->
          optbits & (CAPTURE_W_TCPSPLICE | CAPTURE_W_TCPSTREAM |
                     CAPTURE_W_MULTISTREAM)))
      {
        TIMERTYPE now;
        GETTIME(now);
        while ((GETSECONDS(now) <=
                (GETSECONDS(opt->starting_time) + (long)opt->time)) &&
               get_status_from_opt(opt) == STATUS_RUNNING)
          {
            sleep(1);
            GETTIME(now);
          }
        shutdown_thread(opt);
        pthread_mutex_lock(&(opt->membranch->branchlock));
        pthread_cond_broadcast(&(opt->membranch->busysignal));
        pthread_mutex_unlock(&(opt->membranch->branchlock));
      }
  }
  err = pthread_join(streamer_pthread, NULL);
  if (err < 0)
    {
      printf("ERROR; return code from pthread_join() is %d\n", err);
    }
  else
    D("Streamer thread exit OK");
  LOG("STREAMER: Threads finished. Getting stats for %s\n", opt->filename);
  GETTIME(opt->endtime);

  LOG("Blocking until owned buffers are released\n");
  block_until_free(opt->membranch, opt);
#if(DAEMON)
  LOG("Buffers finished\n");
  if (get_status_from_opt(opt) != STATUS_STOPPED)
    {
      E("Thread didnt finish nicely with STATUS_STOPPED");
      set_status_for_opt(opt, STATUS_FINISHED);
    }
  else
    set_status_for_opt(opt, STATUS_FINISHED);
#else
  close_streamer(opt);
#endif

#if(DAEMON)
  D("Streamer thread exiting for %s", opt->filename);
  pthread_exit(NULL);
#else
  close_opts(opt);
  STREAMER_EXIT;
#endif
}
コード例 #4
0
ファイル: Track.cpp プロジェクト: KingDwag/feedback-editor
void Fretboard::Draw(float time, dBChart *pSong, int track)
{
	MFCALLSTACKc;

	MFView_Push();

	MFRect rect;
	MFView_GetViewport(&rect);

	float aspect = rect.width / rect.height;
	aspect = MFClamp(0.82f, aspect, 2.0f);
	MFView_SetAspectRatio(aspect);

	if(viewPoint == 0)
	{
		// incoming
		MFView_ConfigureProjection(MFDEGREES(65.0f)/aspect, 1.0f, 100.0f);

		// Setup the Camera in 3D space.
		MFMatrix cameraMatrix;
		MFVector start = MakeVector( 0, 8, 3 );
		MFVector dir = MakeVector( 0, 5, -8 );
		dir = (dir-start) * (1.0f/1.777777777f);
		cameraMatrix.LookAt(start + dir*aspect, MakeVector(0.0f, 0.0f, 5.0f));
		MFView_SetCameraMatrix(cameraMatrix);
	}
	else if(viewPoint == 1)
	{
		// overhead
		MFView_ConfigureProjection(MFDEGREES(45.0f), 1.0f, 100.0f);
/*
		float aspect = MFDisplay_GetNativeAspectRatio();
		MFView_SetAspectRatio(aspect);

		MFRect projRect;
		projRect.y = 15;
		projRect.height = -30;
		projRect.x = -projRect.y * aspect;
		projRect.width = -projRect.height * aspect;
		MFView_SetOrtho(&projRect);
*/

		// Setup the Camera in 3D space.
		MFMatrix cameraMatrix;
		cameraMatrix.LookAt(MakeVector(0, 30, 10), MakeVector(0, 0, 10), MakeVector(0, 0, 1));
		MFView_SetCameraMatrix(cameraMatrix);
	}
	else if(viewPoint == 2)
	{
		// overhead
		MFView_ConfigureProjection(MFDEGREES(45.0f), 1.0f, 100.0f);
/*
		float aspect = MFDisplay_GetNativeAspectRatio();
		MFView_SetAspectRatio(aspect);

		MFRect projRect;
		projRect.y = 15;
		projRect.height = -30;
		projRect.x = -projRect.y * aspect;
		projRect.width = -projRect.height * aspect;
		MFView_SetOrtho(&projRect);
*/

		// Setup the Camera in 3D space.
		MFMatrix cameraMatrix;
		cameraMatrix.LookAt(MakeVector(0, 20, 13), MakeVector(0, 0, 13), MakeVector(-1, 0, 0));
		MFView_SetCameraMatrix(cameraMatrix);
	}

	MFView_SetProjection();

	MFMaterial *pFB = pSong->pFretboard ? pSong->pFretboard : pFretboard;
	MFMaterial_SetMaterial(pFB);
	MFPrimitive(PT_TriStrip, 0);

	int start = -4;
	int end = 60;
	int fadeStart = end - 10;

	float fretboardRepeat = 15.0f;
	float fretboardWidth = 7.0f;

	float columnWidth = fretboardWidth / 5.0f;
	float ringBorder = 0.1f;

	// draw the fretboard...
	MFBegin(((end-start) / 4) * 2 + 2);
	MFSetColourV(MFVector::white);

	float halfFB = fretboardWidth*0.5f;

	float offset = time*scrollSpeed;
	float topTime = time + end/scrollSpeed;
	float bottomTime = time + start/scrollSpeed;

	int a;
	float textureOffset = fmodf(offset, fretboardRepeat);
	for(a=start; a<end; a+=4)
	{
		float z = (float)a;
		MFSetTexCoord1(1.0f, 1.0f - (z+textureOffset) / fretboardRepeat);
		MFSetPosition(halfFB, 0.0f, z);
		MFSetTexCoord1(0.0f, 1.0f - (z+textureOffset) / fretboardRepeat);
		MFSetPosition(-halfFB, 0.0f, z);
	}

	float z = (float)a;
	MFSetTexCoord1(1.0f, 1.0f - (z+textureOffset) / fretboardRepeat);
	MFSetPosition(halfFB, 0.0f, z);
	MFSetTexCoord1(0.0f, 1.0f - (z+textureOffset) / fretboardRepeat);
	MFSetPosition(-halfFB, 0.0f, z);

	MFEnd();

	// draw the selection region
	MFMaterial_SetMaterial(pFrets);
	MFPrimitive(PT_TriStrip, 0);

	if(gEditor.selectStart != gEditor.selectEnd)
	{
		float selectStartTime = GETSECONDS(pSong->CalculateTimeOfTick(gEditor.selectStart));
		float selectEndTime = GETSECONDS(pSong->CalculateTimeOfTick(gEditor.selectEnd));

		if(selectStartTime < topTime && selectEndTime > bottomTime)
		{
			selectStartTime = (MFMax(bottomTime, selectStartTime) - time) * scrollSpeed;
			selectEndTime = (MFMin(topTime, selectEndTime) - time) * scrollSpeed;

			MFBegin(4);
			MFSetColour(1.0f, 0.0f, 0.0f, 0.5f);
			MFSetPosition(-halfFB, 0.0f, selectEndTime);
			MFSetPosition(halfFB, 0.0f, selectEndTime);
			MFSetPosition(-halfFB, 0.0f, selectStartTime);
			MFSetPosition(halfFB, 0.0f, selectStartTime);
			MFEnd();
		}
	}

	// draw the fretboard edges and bar lines
	const float barWidth = 0.2f;

	MFMaterial_SetMaterial(pBar);
	MFPrimitive(PT_TriStrip, 0);

	MFBegin(4);
	MFSetColour(0.0f, 0.0f, 0.0f, 0.8f);
	MFSetTexCoord1(0,0);
	MFSetPosition(-halfFB, 0.0f, barWidth);
	MFSetTexCoord1(1,0);
	MFSetPosition(halfFB, 0.0f, barWidth);
	MFSetTexCoord1(0,1);
	MFSetPosition(-halfFB, 0.0f, -barWidth);
	MFSetTexCoord1(1,1);
	MFSetPosition(halfFB, 0.0f, -barWidth);
	MFEnd();

	MFMaterial_SetMaterial(pEdge);
	MFPrimitive(PT_TriStrip, 0);
	MFBegin(34);

	MFSetColour(0.0f, 0.0f, 0.0f, 0.3f);
	for(int col=1; col<5; col++)
	{
		if(col > 1)
			MFSetPosition(-halfFB + columnWidth*(float)col - 0.02f, 0.0f, (float)end);

		MFSetTexCoord1(0,0);
		MFSetPosition(-halfFB + columnWidth*(float)col - 0.02f, 0.0f, (float)end);
		MFSetTexCoord1(1,0);
		MFSetPosition(-halfFB + columnWidth*(float)col + 0.02f, 0.0f, (float)end);
		MFSetTexCoord1(0,1);
		MFSetPosition(-halfFB + columnWidth*(float)col - 0.02f, 0.0f, (float)start);
		MFSetTexCoord1(1,1);
		MFSetPosition(-halfFB + columnWidth*(float)col + 0.02f, 0.0f, (float)start);

		MFSetPosition(-halfFB + columnWidth*(float)col + 0.02f, 0.0f, (float)start);
	}
	MFSetColourV(MFVector::white);
	MFSetPosition(-halfFB - 0.1f, 0.0f, (float)end);

	MFSetTexCoord1(0,0);
	MFSetPosition(-halfFB - 0.1f, 0.0f, (float)end);
	MFSetTexCoord1(1,0);
	MFSetPosition(-halfFB + 0.1f, 0.0f, (float)end);
	MFSetTexCoord1(0,1);
	MFSetPosition(-halfFB - 0.1f, 0.0f, (float)start);
	MFSetTexCoord1(1,1);
	MFSetPosition(-halfFB + 0.1f, 0.0f, (float)start);

	MFSetPosition(-halfFB + 0.1f, 0.0f, (float)start);
	MFSetPosition(halfFB - 0.1f, 0.0f, (float)end);

	MFSetTexCoord1(0,0);
	MFSetPosition(halfFB - 0.1f, 0.0f, (float)end);
	MFSetTexCoord1(1,0);
	MFSetPosition(halfFB + 0.1f, 0.0f, (float)end);
	MFSetTexCoord1(0,1);
	MFSetPosition(halfFB - 0.1f, 0.0f, (float)start);
	MFSetTexCoord1(1,1);
	MFSetPosition(halfFB + 0.1f, 0.0f, (float)start);

	MFEnd();

	// draw the frets....
	MFMaterial_SetMaterial(pBar);
	MFPrimitive(PT_TriStrip, 0);

	int bottomTick = pSong->CalculateTickAtTime((int64)(bottomTime*1000000.0f));
	int res = pSong->GetRes();
	int ticks = bHalfFrets ? res/2 : res;
	int fretBeat = bottomTick + ticks - 1;
	fretBeat -= fretBeat % ticks;
	float fretTime = GETSECONDS(pSong->CalculateTimeOfTick(fretBeat));

	while(fretTime < topTime)
	{
		bool halfBeat = (fretBeat % res) != 0;
		bool bar = false;

		if(!halfBeat)
		{
			GHEvent *pLastTS = pSong->sync.GetMostRecentEvent(GHE_TimeSignature, fretBeat);

			if(pLastTS)
				bar = ((fretBeat - pLastTS->tick) % (pLastTS->parameter*res)) == 0;
			else if(fretBeat == 0)
				bar = true;
		}

		float bw = bar ? barWidth : barWidth*0.5f;
		MFBegin(4);

		float position = (fretTime - time) * scrollSpeed;

		if(!halfBeat)
			MFSetColourV(MFVector::white);
		else
			MFSetColourV(MakeVector(1,1,1,0.3f));
		MFSetTexCoord1(0,0);
		MFSetPosition(-halfFB, 0.0f, position + bw);
		MFSetTexCoord1(1,0);
		MFSetPosition(halfFB, 0.0f, position + bw);
		MFSetTexCoord1(0,1);
		MFSetPosition(-halfFB, 0.0f, position + -bw);
		MFSetTexCoord1(1,1);
		MFSetPosition(halfFB, 0.0f, position + -bw);

		MFEnd();

		fretBeat += ticks;
		fretTime = GETSECONDS(pSong->CalculateTimeOfTick(fretBeat));
	}

	// draw the notes...
	GHEventManager &noteStream = pSong->notes[track];
	GHEvent *pEv = noteStream.First();

	int64 topTimeus = (int64)(topTime*1000000.0f);
	while(pEv && pEv->time < topTimeus)
	{
		if((pEv->event == GHE_Note || pEv->event == GHE_Special) && pEv->tick + pEv->parameter >= bottomTick)
		{
			float evTime = GETSECONDS(pEv->time);

			// TODO: we need to calculate the end of the hold...
			float noteEnd = evTime;
			if(pEv->parameter)
				noteEnd = GETSECONDS(pSong->CalculateTimeOfTick(pEv->tick + pEv->parameter));

			if(pEv->event == GHE_Note && pEv->played != 1)
			{
				// draw a note
				int key = pEv->key;
				bool tap = false;

				// check if there is a previous note, and it is in range
				if(pEv->Prev() && pEv->Prev()->tick < pEv->tick && pEv->Prev()->tick > pEv->tick - (res/2) && pEv->Prev()->key != pEv->key
					&& (!pEv->Next() || !(pEv->Next()->tick == pEv->tick))
					&& !pEv->Prev()->parameter && !(pEv->Prev()->Prev() && pEv->Prev()->Prev()->tick == pEv->Prev()->tick))
				{
					tap = true;
				}

				int noteX = gConfig.controls.leftyFlip[0] ? 4-key : key;

				float position = (GETSECONDS(pEv->time) - time)*scrollSpeed;
				float xoffset = -halfFB + columnWidth*0.5f + (float)noteX*columnWidth;

				if(pEv->parameter)
				{
					MFMaterial_SetMaterial(pFrets);

					float whammyTop = (noteEnd - time)*scrollSpeed;

					MFPrimitive(PT_TriStrip, 0);

					// TODO: we could consider not drawing this part of the hold line.. seems reasonable that it terminates at the line...
					if(gEditor.state == GHPS_Stopped)
					{
						if(position < 0.0f)
						{
							MFBegin(4);
							MFSetColourV(gColours[key]);
							MFSetPosition(xoffset - 0.2f, 0.0f, MFMin(whammyTop, 0.0f));
							MFSetPosition(xoffset + 0.2f, 0.0f, MFMin(whammyTop, 0.0f));
							MFSetPosition(xoffset - 0.2f, 0.0f, position);
							MFSetPosition(xoffset + 0.2f, 0.0f, position);
							MFEnd();
						}
					}

					if(whammyTop > 0.0f)
					{
						// this half could have waves cruising down it if we wanted to support the whammy...
						MFBegin(4);
						MFSetColourV(gColours[key]);
						MFSetPosition(xoffset - 0.2f, 0.0f, MFMin(whammyTop, (float)end));
						MFSetPosition(xoffset + 0.2f, 0.0f, MFMin(whammyTop, (float)end));
						MFSetPosition(xoffset - 0.2f, 0.0f, MFMax(position, 0.0f));
						MFSetPosition(xoffset + 0.2f, 0.0f, MFMax(position, 0.0f));
						MFEnd();
					}
				}

				if(evTime >= bottomTime)
				{
					MFMatrix mat;
					mat.SetScale(MakeVector(0.5f/20, 0.5f/20, 0.5f/20));
					mat.Translate(MakeVector(xoffset, 0.03f, position));
					MFModel_SetWorldMatrix(pButton, mat);

					MFStateBlock *pSB = MFStateBlock_CreateTemporary(64);
					MFStateBlock_SetVector(pSB, MFSCV_DiffuseColour, pEv->played == -1 ? MakeVector(0.3f, 0.3f, 0.3f, 1.0f) : MFVector::white);
//					MFStateBlock_SetVector(pSB, MFSCV_DiffuseColour, position < 0.0f ? MakeVector(0.3f, 0.3f, 0.3f, 1.0f) : MFVector::white);

//					MFRenderer_SetRenderStateOverride(MFRS_MaterialOverride, (uint32&)(tap ? pButtonMat[key] : pButtonRing[key]));
					MFRenderer_AddModel(pButton, pSB, MFView_GetViewState());

					// render the note time
					if(bRenderNoteTimes)
					{
						MFView_Push();
						MFView_SetOrtho(&rect);

						MFVector pos;
						MFView_TransformPoint3DTo2D(MakeVector(xoffset, 0.0f, position), &pos);
						pos.x += 16.0f;
						pos.y -= 26.0f;

						int minutes = (int)(pEv->time / 60000000);
						int seconds = (int)((pEv->time % 60000000) / 1000000);
						int milliseconds = (int)((pEv->time % 1000000) / 1000);
						MFFont_DrawTextf(pText, pos, 20.0f, MFVector::yellow, "%s: %d:%02d.%d\nTick: %g", MFTranslation_GetString(pStrings, TRACK_TIME), minutes, seconds, milliseconds, (float)pEv->tick/res);

						MFView_Pop();
					}
				}
			}

			if(pEv->event == GHE_Special)
			{
//				static MFVector specialColours[3] = { MakeVector(1,0,0,1), MakeVector(1,1,0,1), MakeVector(0,0,1,1) };
//				static float specialX[3] = { halfFB + 0.2f, halfFB + 1, -halfFB - 1 };
//				static float specialWidth[3] = { 0.8f, 0.8f, 0.8f };
				static MFVector specialColours[3] = { MakeVector(1,0,0,0.5f), MakeVector(1,1,0,0.5f), MakeVector(0,0,1,0.5f) };
				static float specialX[3] = { -halfFB, halfFB - 0.8f, -halfFB };
				static float specialWidth[3] = { 0.8f, 0.8f, fretboardWidth };

				float bottom = (evTime - time)*scrollSpeed;
				float top = (noteEnd - time)*scrollSpeed;

				int key = pEv->key;

				MFMaterial_SetMaterial(pFrets);

				MFPrimitive(PT_TriStrip, 0);
				MFBegin(4);
				MFSetColourV(specialColours[key]);
				MFSetPosition(specialX[key], 0.0f, MFMin(top, (float)end));
				MFSetPosition(specialX[key]+specialWidth[key], 0.0f, MFMin(top, (float)end));
				MFSetPosition(specialX[key], 0.0f, MFMax(bottom, (float)start));
				MFSetPosition(specialX[key]+specialWidth[key], 0.0f, MFMax(bottom, (float)start));
				MFEnd();
			}
		}

		pEv = pEv->Next();
	}

//	MFRenderer_SetRenderStateOverride(MFRS_MaterialOverride, NULL);

	// draw circles at the bottom..
	MFMaterial_SetMaterial(pRing);
	for(int a=0; a<5; a++)
	{
		DrawRing(-halfFB + (float)a*columnWidth + columnWidth*ringBorder, 0.0f, columnWidth*(1.0f-ringBorder*2));
	}

	for(int a=0; a<5; a++)
	{
		dBControlType keys_righty[] = { dBCtrl_Edit_Note0, dBCtrl_Edit_Note1, dBCtrl_Edit_Note2, dBCtrl_Edit_Note3, dBCtrl_Edit_Note4 };
		dBControlType keys_lefty[] = { dBCtrl_Edit_Note4, dBCtrl_Edit_Note3, dBCtrl_Edit_Note2, dBCtrl_Edit_Note1, dBCtrl_Edit_Note0 };
		dBControlType *keys = gConfig.controls.leftyFlip[0] ? keys_lefty : keys_righty;

		int ringPos = gConfig.controls.leftyFlip[0] ? 4-a : a;

		MFMaterial_SetMaterial(pColourRing[a]);
		DrawRing(-halfFB + (float)ringPos*columnWidth, 0.0f, columnWidth, !TestControl(keys[a], GHCT_Hold));
	}

	// render trigger particles
	MFParticleSystem_Draw(pParticles);

	// render text and stuff
	MFView_SetOrtho(&rect);

	pEv = pSong->sync.GetNextEvent(bottomTick);

	while(pEv && pEv->time < topTimeus)
	{
		float evTime = GETSECONDS(pEv->time);

		if(evTime > bottomTime)
		{
			if(pEv->event == GHE_BPM)
			{
				float position = (evTime - time) * scrollSpeed;

				MFVector pos;
				MFView_TransformPoint3DTo2D(MakeVector(halfFB + 0.2f, 0.0f, position), &pos);
				pos.y -= 12.0f;
				MFFont_DrawTextf(pText, pos, 24.0f, MakeVector(0,0.5f,0,1), "%s: %g", MFTranslation_GetString(pStrings, TRACK_BPM), (float)pEv->parameter * 0.001f);
			}
			if(pEv->event == GHE_Anchor)
			{
				int minutes = (int)(pEv->time / 60000000);
				int seconds = (int)((pEv->time%60000000)/1000000);
				int milliseconds = (int)((pEv->time%1000000)/1000);

				float position = (evTime - time) * scrollSpeed;

				MFVector pos;
				MFView_TransformPoint3DTo2D(MakeVector(halfFB + 0.2f, 0.0f, position), &pos);
				pos.y -= 12.0f;
				MFFont_DrawTextf(pText, pos, 24.0f, MakeVector(0,0.5f,0,1), "A: %02d:%02d.%03d\n   %s: %g", minutes, seconds, milliseconds, MFTranslation_GetString(pStrings, TRACK_BPM), (float)pEv->parameter * 0.001f);
			}
			else if(pEv->event == GHE_TimeSignature)
			{
				float position = (evTime - time) * scrollSpeed;

				MFVector pos;
				MFView_TransformPoint3DTo2D(MakeVector(-halfFB - 0.2f, 0.0f, position), &pos);
				const char *pString = MFStr("TS: %d/4", pEv->parameter);
				pos.x -= MFFont_GetStringWidth(pText, pString, 24.0f);
				pos.y -= 12.0f;
				MFFont_DrawTextf(pText, pos, 24.0f, MFVector::yellow, pString);
			}
		}

		pEv = pEv->Next();
	}

	// render events
	pEv = pSong->events.GetNextEvent(bottomTick);

	int lastChecked = -1;
	float yEventOffset = -12.0f;
	while(pEv && pEv->time < topTimeus)
	{
		float evTime = GETSECONDS(pEv->time);

		if(evTime > bottomTime)
		{
			if(pEv->event == GHE_Event)
			{
				if(lastChecked != pEv->tick)
				{
					yEventOffset = -12.0f;
					lastChecked = pEv->tick;

					if(pSong->sync.FindEvent(GHE_TimeSignature, pEv->tick, 0))
					{
						yEventOffset -= 24.0f;
					}
				}

				float position = (evTime - time) * scrollSpeed;

				MFVector pos;
				MFView_TransformPoint3DTo2D(MakeVector(-halfFB - 0.2f, 0.0f, position), &pos);

				if(!MFString_CompareN(pEv->GetString(), "section ", 8))
				{
					// draw a line across?

					pos.x -= MFFont_GetStringWidth(pText, &pEv->GetString()[8], 24.0f);
					pos.y += yEventOffset;
					MFFont_DrawTextf(pText, pos, 24.0f, MFVector::blue, &pEv->GetString()[8]);
				}
				else
				{
					pos.x -= MFFont_GetStringWidth(pText, pEv->GetString(), 24.0f);
					pos.y += yEventOffset;
					MFFont_DrawTextf(pText, pos, 24.0f, MFVector::white, pEv->GetString());
				}

				yEventOffset -= 24.0f;
			}
		}

		pEv = pEv->Next();
	}

	// render track events
	pEv = pSong->notes[track].GetNextEvent(bottomTick);

	lastChecked = -1;
	yEventOffset = -12.0f;
	while(pEv && pEv->time < topTimeus)
	{
		float evTime = GETSECONDS(pEv->time);

		if(evTime > bottomTime)
		{
			if(pEv->event == GHE_Event)
			{
				if(lastChecked != pEv->tick)
				{
					yEventOffset = -12.0f;
					lastChecked = pEv->tick;

					if(pSong->sync.FindEvent(GHE_TimeSignature, pEv->tick, 0))
					{
						yEventOffset -= 24.0f;
					}

					GHEvent *pOther = pSong->events.FindEvent(GHE_Event, pEv->tick, 0);
					while(pOther && pOther->tick == pEv->tick)
					{
						yEventOffset -= 24.0f;
						pOther = pOther->Next();
					}
				}

				float position = (evTime - time) * scrollSpeed;

				MFVector pos;
				MFView_TransformPoint3DTo2D(MakeVector(-halfFB - 0.2f, 0.0f, position), &pos);

				pos.x -= MFFont_GetStringWidth(pText, pEv->GetString(), 24.0f);
				pos.y += yEventOffset;
				MFFont_DrawTextf(pText, pos, 24.0f, MakeVector(0.6f, 0.8f, 1.0f, 1.0f), pEv->GetString());

				yEventOffset -= 24.0f;
			}
		}

		pEv = pEv->Next();
	}

	MFView_Pop();
}