/[svn]/libgig/trunk/src/gig.cpp
ViewVC logotype

Diff of /libgig/trunk/src/gig.cpp

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 27 by schoenebeck, Thu Jan 1 23:46:41 2004 UTC revision 308 by schoenebeck, Sun Nov 21 18:02:21 2004 UTC
# Line 2  Line 2 
2   *                                                                         *   *                                                                         *
3   *   libgig - C++ cross-platform Gigasampler format file loader library    *   *   libgig - C++ cross-platform Gigasampler format file loader library    *
4   *                                                                         *   *                                                                         *
5   *   Copyright (C) 2003 by Christian Schoenebeck                           *   *   Copyright (C) 2003, 2004 by Christian Schoenebeck                     *
6   *                         <cuse@users.sourceforge.net>                    *   *                               <cuse@users.sourceforge.net>              *
7   *                                                                         *   *                                                                         *
8   *   This library is free software; you can redistribute it and/or modify  *   *   This library is free software; you can redistribute it and/or modify  *
9   *   it under the terms of the GNU General Public License as published by  *   *   it under the terms of the GNU General Public License as published by  *
# Line 680  namespace gig { Line 680  namespace gig {
680          if (!pVelocityTables) pVelocityTables = new VelocityTableMap;          if (!pVelocityTables) pVelocityTables = new VelocityTableMap;
681    
682          RIFF::Chunk* _3ewa = _3ewl->GetSubChunk(CHUNK_ID_3EWA);          RIFF::Chunk* _3ewa = _3ewl->GetSubChunk(CHUNK_ID_3EWA);
683          _3ewa->ReadInt32(); // unknown, allways 0x0000008C ?          _3ewa->ReadInt32(); // unknown, always 0x0000008C ?
684          LFO3Frequency = (double) GIG_EXP_DECODE(_3ewa->ReadInt32());          LFO3Frequency = (double) GIG_EXP_DECODE(_3ewa->ReadInt32());
685          EG3Attack     = (double) GIG_EXP_DECODE(_3ewa->ReadInt32());          EG3Attack     = (double) GIG_EXP_DECODE(_3ewa->ReadInt32());
686          _3ewa->ReadInt16(); // unknown          _3ewa->ReadInt16(); // unknown
# Line 696  namespace gig { Line 696  namespace gig {
696          _3ewa->ReadInt16(); // unknown          _3ewa->ReadInt16(); // unknown
697          EG1Sustain          = _3ewa->ReadUint16();          EG1Sustain          = _3ewa->ReadUint16();
698          EG1Release          = (double) GIG_EXP_DECODE(_3ewa->ReadInt32());          EG1Release          = (double) GIG_EXP_DECODE(_3ewa->ReadInt32());
699          EG1Controller       = static_cast<eg1_ctrl_t>(_3ewa->ReadUint8());          EG1Controller       = DecodeLeverageController(static_cast<_lev_ctrl_t>(_3ewa->ReadUint8()));
700          uint8_t eg1ctrloptions        = _3ewa->ReadUint8();          uint8_t eg1ctrloptions        = _3ewa->ReadUint8();
701          EG1ControllerInvert           = eg1ctrloptions & 0x01;          EG1ControllerInvert           = eg1ctrloptions & 0x01;
702          EG1ControllerAttackInfluence  = GIG_EG_CTR_ATTACK_INFLUENCE_EXTRACT(eg1ctrloptions);          EG1ControllerAttackInfluence  = GIG_EG_CTR_ATTACK_INFLUENCE_EXTRACT(eg1ctrloptions);
703          EG1ControllerDecayInfluence   = GIG_EG_CTR_DECAY_INFLUENCE_EXTRACT(eg1ctrloptions);          EG1ControllerDecayInfluence   = GIG_EG_CTR_DECAY_INFLUENCE_EXTRACT(eg1ctrloptions);
704          EG1ControllerReleaseInfluence = GIG_EG_CTR_RELEASE_INFLUENCE_EXTRACT(eg1ctrloptions);          EG1ControllerReleaseInfluence = GIG_EG_CTR_RELEASE_INFLUENCE_EXTRACT(eg1ctrloptions);
705          EG2Controller       = static_cast<eg2_ctrl_t>(_3ewa->ReadUint8());          EG2Controller       = DecodeLeverageController(static_cast<_lev_ctrl_t>(_3ewa->ReadUint8()));
706          uint8_t eg2ctrloptions        = _3ewa->ReadUint8();          uint8_t eg2ctrloptions        = _3ewa->ReadUint8();
707          EG2ControllerInvert           = eg2ctrloptions & 0x01;          EG2ControllerInvert           = eg2ctrloptions & 0x01;
708          EG2ControllerAttackInfluence  = GIG_EG_CTR_ATTACK_INFLUENCE_EXTRACT(eg2ctrloptions);          EG2ControllerAttackInfluence  = GIG_EG_CTR_ATTACK_INFLUENCE_EXTRACT(eg2ctrloptions);
# Line 764  namespace gig { Line 764  namespace gig {
764              ReleaseVelocityResponseDepth = 0;              ReleaseVelocityResponseDepth = 0;
765          }          }
766          VelocityResponseCurveScaling = _3ewa->ReadUint8();          VelocityResponseCurveScaling = _3ewa->ReadUint8();
767          AttenuationControlTreshold   = _3ewa->ReadInt8();          AttenuationControllerThreshold = _3ewa->ReadInt8();
768          _3ewa->ReadInt32(); // unknown          _3ewa->ReadInt32(); // unknown
769          SampleStartOffset = (uint16_t) _3ewa->ReadInt16();          SampleStartOffset = (uint16_t) _3ewa->ReadInt16();
770          _3ewa->ReadInt16(); // unknown          _3ewa->ReadInt16(); // unknown
# Line 774  namespace gig { Line 774  namespace gig {
774          else if (pitchTrackDimensionBypass & 0x20) DimensionBypass = dim_bypass_ctrl_95;          else if (pitchTrackDimensionBypass & 0x20) DimensionBypass = dim_bypass_ctrl_95;
775          else                                       DimensionBypass = dim_bypass_ctrl_none;          else                                       DimensionBypass = dim_bypass_ctrl_none;
776          uint8_t pan = _3ewa->ReadUint8();          uint8_t pan = _3ewa->ReadUint8();
777          Pan         = (pan < 64) ? pan : (-1) * (int8_t)pan - 63;          Pan         = (pan < 64) ? pan : -((int)pan - 63); // signed 7 bit -> signed 8 bit
778          SelfMask = _3ewa->ReadInt8() & 0x01;          SelfMask = _3ewa->ReadInt8() & 0x01;
779          _3ewa->ReadInt8(); // unknown          _3ewa->ReadInt8(); // unknown
780          uint8_t lfo3ctrl = _3ewa->ReadUint8();          uint8_t lfo3ctrl = _3ewa->ReadUint8();
781          LFO3Controller           = static_cast<lfo3_ctrl_t>(lfo3ctrl & 0x07); // lower 3 bits          LFO3Controller           = static_cast<lfo3_ctrl_t>(lfo3ctrl & 0x07); // lower 3 bits
782          LFO3Sync                 = lfo3ctrl & 0x20; // bit 5          LFO3Sync                 = lfo3ctrl & 0x20; // bit 5
783          InvertAttenuationControl = lfo3ctrl & 0x80; // bit 7          InvertAttenuationController = lfo3ctrl & 0x80; // bit 7
784          if (VCFType == vcf_type_lowpass) {          if (VCFType == vcf_type_lowpass) {
785              if (lfo3ctrl & 0x40) // bit 6              if (lfo3ctrl & 0x40) // bit 6
786                  VCFType = vcf_type_lowpassturbo;                  VCFType = vcf_type_lowpassturbo;
787          }          }
788          AttenuationControl = static_cast<attenuation_ctrl_t>(_3ewa->ReadUint8());          AttenuationController  = DecodeLeverageController(static_cast<_lev_ctrl_t>(_3ewa->ReadUint8()));
789          uint8_t lfo2ctrl       = _3ewa->ReadUint8();          uint8_t lfo2ctrl       = _3ewa->ReadUint8();
790          LFO2Controller         = static_cast<lfo2_ctrl_t>(lfo2ctrl & 0x07); // lower 3 bits          LFO2Controller         = static_cast<lfo2_ctrl_t>(lfo2ctrl & 0x07); // lower 3 bits
791          LFO2FlipPhase          = lfo2ctrl & 0x80; // bit 7          LFO2FlipPhase          = lfo2ctrl & 0x80; // bit 7
# Line 836  namespace gig { Line 836  namespace gig {
836              pVelocityAttenuationTable = (*pVelocityTables)[tableKey];              pVelocityAttenuationTable = (*pVelocityTables)[tableKey];
837          }          }
838          else {          else {
839              pVelocityAttenuationTable = new double[128];              pVelocityAttenuationTable =
840              switch (VelocityResponseCurve) { // calculate the new table                  CreateVelocityTable(VelocityResponseCurve,
841                  case curve_type_nonlinear:                                      VelocityResponseDepth,
842                      for (int velocity = 0; velocity < 128; velocity++) {                                      VelocityResponseCurveScaling);
                         pVelocityAttenuationTable[velocity] =  
                             GIG_VELOCITY_TRANSFORM_NONLINEAR((double)(velocity+1),(double)(VelocityResponseDepth+1),(double)VelocityResponseCurveScaling);  
                         if      (pVelocityAttenuationTable[velocity] > 1.0) pVelocityAttenuationTable[velocity] = 1.0;  
                         else if (pVelocityAttenuationTable[velocity] < 0.0) pVelocityAttenuationTable[velocity] = 0.0;  
                      }  
                      break;  
                 case curve_type_linear:  
                     for (int velocity = 0; velocity < 128; velocity++) {  
                         pVelocityAttenuationTable[velocity] =  
                             GIG_VELOCITY_TRANSFORM_LINEAR((double)velocity,(double)(VelocityResponseDepth+1),(double)VelocityResponseCurveScaling);  
                         if      (pVelocityAttenuationTable[velocity] > 1.0) pVelocityAttenuationTable[velocity] = 1.0;  
                         else if (pVelocityAttenuationTable[velocity] < 0.0) pVelocityAttenuationTable[velocity] = 0.0;  
                     }  
                     break;  
                 case curve_type_special:  
                     for (int velocity = 0; velocity < 128; velocity++) {  
                         pVelocityAttenuationTable[velocity] =  
                             GIG_VELOCITY_TRANSFORM_SPECIAL((double)(velocity+1),(double)(VelocityResponseDepth+1),(double)VelocityResponseCurveScaling);  
                         if      (pVelocityAttenuationTable[velocity] > 1.0) pVelocityAttenuationTable[velocity] = 1.0;  
                         else if (pVelocityAttenuationTable[velocity] < 0.0) pVelocityAttenuationTable[velocity] = 0.0;  
                     }  
                     break;  
                 case curve_type_unknown:  
                 default:  
                     throw gig::Exception("Unknown transform curve type.");  
             }  
843              (*pVelocityTables)[tableKey] = pVelocityAttenuationTable; // put the new table into the tables map              (*pVelocityTables)[tableKey] = pVelocityAttenuationTable; // put the new table into the tables map
844          }          }
845      }      }
846    
847        leverage_ctrl_t DimensionRegion::DecodeLeverageController(_lev_ctrl_t EncodedController) {
848            leverage_ctrl_t decodedcontroller;
849            switch (EncodedController) {
850                // special controller
851                case _lev_ctrl_none:
852                    decodedcontroller.type = leverage_ctrl_t::type_none;
853                    decodedcontroller.controller_number = 0;
854                    break;
855                case _lev_ctrl_velocity:
856                    decodedcontroller.type = leverage_ctrl_t::type_velocity;
857                    decodedcontroller.controller_number = 0;
858                    break;
859                case _lev_ctrl_channelaftertouch:
860                    decodedcontroller.type = leverage_ctrl_t::type_channelaftertouch;
861                    decodedcontroller.controller_number = 0;
862                    break;
863    
864                // ordinary MIDI control change controller
865                case _lev_ctrl_modwheel:
866                    decodedcontroller.type = leverage_ctrl_t::type_controlchange;
867                    decodedcontroller.controller_number = 1;
868                    break;
869                case _lev_ctrl_breath:
870                    decodedcontroller.type = leverage_ctrl_t::type_controlchange;
871                    decodedcontroller.controller_number = 2;
872                    break;
873                case _lev_ctrl_foot:
874                    decodedcontroller.type = leverage_ctrl_t::type_controlchange;
875                    decodedcontroller.controller_number = 4;
876                    break;
877                case _lev_ctrl_effect1:
878                    decodedcontroller.type = leverage_ctrl_t::type_controlchange;
879                    decodedcontroller.controller_number = 12;
880                    break;
881                case _lev_ctrl_effect2:
882                    decodedcontroller.type = leverage_ctrl_t::type_controlchange;
883                    decodedcontroller.controller_number = 13;
884                    break;
885                case _lev_ctrl_genpurpose1:
886                    decodedcontroller.type = leverage_ctrl_t::type_controlchange;
887                    decodedcontroller.controller_number = 16;
888                    break;
889                case _lev_ctrl_genpurpose2:
890                    decodedcontroller.type = leverage_ctrl_t::type_controlchange;
891                    decodedcontroller.controller_number = 17;
892                    break;
893                case _lev_ctrl_genpurpose3:
894                    decodedcontroller.type = leverage_ctrl_t::type_controlchange;
895                    decodedcontroller.controller_number = 18;
896                    break;
897                case _lev_ctrl_genpurpose4:
898                    decodedcontroller.type = leverage_ctrl_t::type_controlchange;
899                    decodedcontroller.controller_number = 19;
900                    break;
901                case _lev_ctrl_portamentotime:
902                    decodedcontroller.type = leverage_ctrl_t::type_controlchange;
903                    decodedcontroller.controller_number = 5;
904                    break;
905                case _lev_ctrl_sustainpedal:
906                    decodedcontroller.type = leverage_ctrl_t::type_controlchange;
907                    decodedcontroller.controller_number = 64;
908                    break;
909                case _lev_ctrl_portamento:
910                    decodedcontroller.type = leverage_ctrl_t::type_controlchange;
911                    decodedcontroller.controller_number = 65;
912                    break;
913                case _lev_ctrl_sostenutopedal:
914                    decodedcontroller.type = leverage_ctrl_t::type_controlchange;
915                    decodedcontroller.controller_number = 66;
916                    break;
917                case _lev_ctrl_softpedal:
918                    decodedcontroller.type = leverage_ctrl_t::type_controlchange;
919                    decodedcontroller.controller_number = 67;
920                    break;
921                case _lev_ctrl_genpurpose5:
922                    decodedcontroller.type = leverage_ctrl_t::type_controlchange;
923                    decodedcontroller.controller_number = 80;
924                    break;
925                case _lev_ctrl_genpurpose6:
926                    decodedcontroller.type = leverage_ctrl_t::type_controlchange;
927                    decodedcontroller.controller_number = 81;
928                    break;
929                case _lev_ctrl_genpurpose7:
930                    decodedcontroller.type = leverage_ctrl_t::type_controlchange;
931                    decodedcontroller.controller_number = 82;
932                    break;
933                case _lev_ctrl_genpurpose8:
934                    decodedcontroller.type = leverage_ctrl_t::type_controlchange;
935                    decodedcontroller.controller_number = 83;
936                    break;
937                case _lev_ctrl_effect1depth:
938                    decodedcontroller.type = leverage_ctrl_t::type_controlchange;
939                    decodedcontroller.controller_number = 91;
940                    break;
941                case _lev_ctrl_effect2depth:
942                    decodedcontroller.type = leverage_ctrl_t::type_controlchange;
943                    decodedcontroller.controller_number = 92;
944                    break;
945                case _lev_ctrl_effect3depth:
946                    decodedcontroller.type = leverage_ctrl_t::type_controlchange;
947                    decodedcontroller.controller_number = 93;
948                    break;
949                case _lev_ctrl_effect4depth:
950                    decodedcontroller.type = leverage_ctrl_t::type_controlchange;
951                    decodedcontroller.controller_number = 94;
952                    break;
953                case _lev_ctrl_effect5depth:
954                    decodedcontroller.type = leverage_ctrl_t::type_controlchange;
955                    decodedcontroller.controller_number = 95;
956                    break;
957    
958                // unknown controller type
959                default:
960                    throw gig::Exception("Unknown leverage controller type.");
961            }
962            return decodedcontroller;
963        }
964    
965      DimensionRegion::~DimensionRegion() {      DimensionRegion::~DimensionRegion() {
966          Instances--;          Instances--;
967          if (!Instances) {          if (!Instances) {
# Line 893  namespace gig { Line 985  namespace gig {
985       * triggered to get the volume with which the sample should be played       * triggered to get the volume with which the sample should be played
986       * back.       * back.
987       *       *
988       * @param    MIDI velocity value of the triggered key (between 0 and 127)       * @param MIDIKeyVelocity  MIDI velocity value of the triggered key (between 0 and 127)
989       * @returns  amplitude factor (between 0.0 and 1.0)       * @returns                amplitude factor (between 0.0 and 1.0)
990       */       */
991      double DimensionRegion::GetVelocityAttenuation(uint8_t MIDIKeyVelocity) {      double DimensionRegion::GetVelocityAttenuation(uint8_t MIDIKeyVelocity) {
992          return pVelocityAttenuationTable[MIDIKeyVelocity];          return pVelocityAttenuationTable[MIDIKeyVelocity];
993      }      }
994    
995        double* DimensionRegion::CreateVelocityTable(curve_type_t curveType, uint8_t depth, uint8_t scaling) {
996            
997            // line-segment approximations of the 15 velocity curves
998    
999            // linear
1000            const int lin0[] = { 1, 1, 127, 127 };
1001            const int lin1[] = { 1, 21, 127, 127 };
1002            const int lin2[] = { 1, 45, 127, 127 };
1003            const int lin3[] = { 1, 74, 127, 127 };
1004            const int lin4[] = { 1, 127, 127, 127 };
1005    
1006            // non-linear
1007            const int non0[] = { 1, 4, 24, 5, 57, 17, 92, 57, 122, 127, 127, 127 };
1008            const int non1[] = { 1, 4, 46, 9, 93, 56, 118, 106, 123, 127,
1009                                 127, 127 };
1010            const int non2[] = { 1, 4, 46, 9, 57, 20, 102, 107, 107, 127,
1011                                 127, 127 };
1012            const int non3[] = { 1, 15, 10, 19, 67, 73, 80, 80, 90, 98, 98, 127,
1013                                 127, 127 };
1014            const int non4[] = { 1, 25, 33, 57, 82, 81, 92, 127, 127, 127 };
1015            
1016            // special
1017            const int spe0[] = { 1, 2, 76, 10, 90, 15, 95, 20, 99, 28, 103, 44,
1018                                 113, 127, 127, 127 };
1019            const int spe1[] = { 1, 2, 27, 5, 67, 18, 89, 29, 95, 35, 107, 67,
1020                                 118, 127, 127, 127 };
1021            const int spe2[] = { 1, 1, 33, 1, 53, 5, 61, 13, 69, 32, 79, 74,
1022                                 85, 90, 91, 127, 127, 127 };
1023            const int spe3[] = { 1, 32, 28, 35, 66, 48, 89, 59, 95, 65, 99, 73,
1024                                 117, 127, 127, 127 };
1025            const int spe4[] = { 1, 4, 23, 5, 49, 13, 57, 17, 92, 57, 122, 127,
1026                                 127, 127 };
1027            
1028            const int* const curves[] = { non0, non1, non2, non3, non4,
1029                                          lin0, lin1, lin2, lin3, lin4,
1030                                          spe0, spe1, spe2, spe3, spe4 };
1031            
1032            double* const table = new double[128];
1033    
1034            const int* curve = curves[curveType * 5 + depth];
1035            const int s = scaling == 0 ? 20 : scaling; // 0 or 20 means no scaling
1036            
1037            table[0] = 0;
1038            for (int x = 1 ; x < 128 ; x++) {
1039    
1040                if (x > curve[2]) curve += 2;
1041                double y = curve[1] + (x - curve[0]) *
1042                    (double(curve[3] - curve[1]) / (curve[2] - curve[0]));
1043                y = y / 127;
1044    
1045                // Scale up for s > 20, down for s < 20. When
1046                // down-scaling, the curve still ends at 1.0.
1047                if (s < 20 && y >= 0.5)
1048                    y = y / ((2 - 40.0 / s) * y + 40.0 / s - 1);
1049                else
1050                    y = y * (s / 20.0);
1051                if (y > 1) y = 1;
1052    
1053                table[x] = y;
1054            }
1055            return table;
1056        }
1057    
1058    
1059  // *************** Region ***************  // *************** Region ***************
# Line 911  namespace gig { Line 1065  namespace gig {
1065          for (int i = 0; i < 32; i++) {          for (int i = 0; i < 32; i++) {
1066              pDimensionRegions[i] = NULL;              pDimensionRegions[i] = NULL;
1067          }          }
1068            Layers = 1;
1069    
1070          // Actual Loading          // Actual Loading
1071    
# Line 935  namespace gig { Line 1090  namespace gig {
1090                      pDimensionDefinitions[i].bits      = bits;                      pDimensionDefinitions[i].bits      = bits;
1091                      pDimensionDefinitions[i].zones     = 0x01 << bits; // = pow(2,bits)                      pDimensionDefinitions[i].zones     = 0x01 << bits; // = pow(2,bits)
1092                      pDimensionDefinitions[i].split_type = (dimension == dimension_layer ||                      pDimensionDefinitions[i].split_type = (dimension == dimension_layer ||
1093                                                             dimension == dimension_samplechannel) ? split_type_bit                                                             dimension == dimension_samplechannel ||
1094                                                                                                   : split_type_normal;                                                             dimension == dimension_releasetrigger) ? split_type_bit
1095                                                                                                      : split_type_normal;
1096                      pDimensionDefinitions[i].ranges = NULL; // it's not possible to check velocity dimensions for custom defined ranges at this point                      pDimensionDefinitions[i].ranges = NULL; // it's not possible to check velocity dimensions for custom defined ranges at this point
1097                      pDimensionDefinitions[i].zone_size  =                      pDimensionDefinitions[i].zone_size  =
1098                          (pDimensionDefinitions[i].split_type == split_type_normal) ? 128 / pDimensionDefinitions[i].zones                          (pDimensionDefinitions[i].split_type == split_type_normal) ? 128 / pDimensionDefinitions[i].zones
1099                                                                                     : 0;                                                                                     : 0;
1100                      Dimensions++;                      Dimensions++;
1101    
1102                        // if this is a layer dimension, remember the amount of layers
1103                        if (dimension == dimension_layer) Layers = pDimensionDefinitions[i].zones;
1104                  }                  }
1105                  _3lnk->SetPos(6, RIFF::stream_curpos); // jump forward to next dimension definition                  _3lnk->SetPos(6, RIFF::stream_curpos); // jump forward to next dimension definition
1106              }              }
# Line 1034  namespace gig { Line 1193  namespace gig {
1193       * @see             Dimensions       * @see             Dimensions
1194       */       */
1195      DimensionRegion* Region::GetDimensionRegionByValue(uint Dim4Val, uint Dim3Val, uint Dim2Val, uint Dim1Val, uint Dim0Val) {      DimensionRegion* Region::GetDimensionRegionByValue(uint Dim4Val, uint Dim3Val, uint Dim2Val, uint Dim1Val, uint Dim0Val) {
1196          unsigned int bits[5] = {Dim0Val,Dim1Val,Dim2Val,Dim3Val,Dim4Val};          uint8_t bits[5] = {Dim0Val,Dim1Val,Dim2Val,Dim3Val,Dim4Val};
1197          for (uint i = 0; i < Dimensions; i++) {          for (uint i = 0; i < Dimensions; i++) {
1198              switch (pDimensionDefinitions[i].split_type) {              switch (pDimensionDefinitions[i].split_type) {
1199                  case split_type_normal:                  case split_type_normal:
# Line 1043  namespace gig { Line 1202  namespace gig {
1202                  case split_type_customvelocity:                  case split_type_customvelocity:
1203                      bits[i] = VelocityTable[bits[i]];                      bits[i] = VelocityTable[bits[i]];
1204                      break;                      break;
1205                  // else the value is already the sought dimension bit number                  case split_type_bit: // the value is already the sought dimension bit number
1206                        const uint8_t limiter_mask = (0xff << pDimensionDefinitions[i].bits) ^ 0xff;
1207                        bits[i] = bits[i] & limiter_mask; // just make sure the value don't uses more bits than allowed
1208                        break;
1209              }              }
1210          }          }
1211          return GetDimensionRegionByBit(bits[4],bits[3],bits[2],bits[1],bits[0]);          return GetDimensionRegionByBit(bits[4],bits[3],bits[2],bits[1],bits[0]);

Legend:
Removed from v.27  
changed lines
  Added in v.308

  ViewVC Help
Powered by ViewVC