/[svn]/linuxsampler/trunk/src/engines/EngineBase.h
ViewVC logotype

Diff of /linuxsampler/trunk/src/engines/EngineBase.h

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

revision 2879 by schoenebeck, Tue Apr 19 14:07:53 2016 UTC revision 3207 by schoenebeck, Thu May 25 10:53:45 2017 UTC
# Line 5  Line 5 
5   *   Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck    *   *   Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck    *
6   *   Copyright (C) 2005-2008 Christian Schoenebeck                         *   *   Copyright (C) 2005-2008 Christian Schoenebeck                         *
7   *   Copyright (C) 2009-2012 Christian Schoenebeck and Grigor Iliev        *   *   Copyright (C) 2009-2012 Christian Schoenebeck and Grigor Iliev        *
8   *   Copyright (C) 2012-2016 Christian Schoenebeck and Andreas Persson     *   *   Copyright (C) 2012-2017 Christian Schoenebeck and Andreas Persson     *
9   *                                                                         *   *                                                                         *
10   *   This program is free software; you can redistribute it and/or modify  *   *   This program is free software; you can redistribute it and/or modify  *
11   *   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 58  namespace LinuxSampler { Line 58  namespace LinuxSampler {
58              typedef typename RTList<RR*>::Iterator RootRegionIterator;              typedef typename RTList<RR*>::Iterator RootRegionIterator;
59              typedef typename MidiKeyboardManager<V>::MidiKey MidiKey;              typedef typename MidiKeyboardManager<V>::MidiKey MidiKey;
60                            
61              EngineBase() : SuspendedRegions(128), noteIDPool(GLOBAL_MAX_NOTES) {              EngineBase() : noteIDPool(GLOBAL_MAX_NOTES), SuspendedRegions(128) {
62                  pDiskThread          = NULL;                  pDiskThread          = NULL;
63                  pNotePool            = new Pool< Note<V> >(GLOBAL_MAX_NOTES);                  pNotePool            = new Pool< Note<V> >(GLOBAL_MAX_NOTES);
64                  pNotePool->setPoolElementIDsReservedBits(INSTR_SCRIPT_EVENT_ID_RESERVED_BITS);                  pNotePool->setPoolElementIDsReservedBits(INSTR_SCRIPT_EVENT_ID_RESERVED_BITS);
# Line 163  namespace LinuxSampler { Line 163  namespace LinuxSampler {
163                                  dmsg(5,("Engine: Sysex received\n"));                                  dmsg(5,("Engine: Sysex received\n"));
164                                  ProcessSysex(itEvent);                                  ProcessSysex(itEvent);
165                                  break;                                  break;
166                                default: ; // noop
167                          }                          }
168                      }                      }
169                  }                  }
# Line 596  namespace LinuxSampler { Line 597  namespace LinuxSampler {
597              }              }
598    
599              // implementation of abstract method derived from class 'LinuxSampler::RegionPools'              // implementation of abstract method derived from class 'LinuxSampler::RegionPools'
600              virtual Pool<R*>* GetRegionPool(int index) {              virtual Pool<R*>* GetRegionPool(int index) OVERRIDE {
601                  if (index < 0 || index > 1) throw Exception("Index out of bounds");                  if (index < 0 || index > 1) throw Exception("Index out of bounds");
602                  return pRegionPool[index];                  return pRegionPool[index];
603              }              }
# Line 604  namespace LinuxSampler { Line 605  namespace LinuxSampler {
605              // implementation of abstract methods derived from class 'LinuxSampler::NotePool'              // implementation of abstract methods derived from class 'LinuxSampler::NotePool'
606              virtual Pool<V>* GetVoicePool() OVERRIDE { return pVoicePool; }              virtual Pool<V>* GetVoicePool() OVERRIDE { return pVoicePool; }
607              virtual Pool< Note<V> >* GetNotePool() OVERRIDE { return pNotePool; }              virtual Pool< Note<V> >* GetNotePool() OVERRIDE { return pNotePool; }
608              virtual Pool<note_id_t>* GetNodeIDPool() OVERRIDE { return &noteIDPool; }              virtual Pool<note_id_t>* GetNoteIDPool() OVERRIDE { return &noteIDPool; }
609    
610              D* GetDiskThread() { return pDiskThread; }              D* GetDiskThread() { return pDiskThread; }
611    
# Line 675  namespace LinuxSampler { Line 676  namespace LinuxSampler {
676               * @param pNoteOnEvent - event which caused this               * @param pNoteOnEvent - event which caused this
677               * @returns new note's unique ID (or zero on error)               * @returns new note's unique ID (or zero on error)
678               */               */
679              note_id_t LaunchNewNote(LinuxSampler::EngineChannel* pEngineChannel, Event* pNoteOnEvent) OVERRIDE {              note_id_t LaunchNewNote(LinuxSampler::EngineChannel* pEngineChannel, Pool<Event>::Iterator& itNoteOnEvent) OVERRIDE {
680                  EngineChannelBase<V, R, I>* pChannel = static_cast<EngineChannelBase<V, R, I>*>(pEngineChannel);                  EngineChannelBase<V, R, I>* pChannel = static_cast<EngineChannelBase<V, R, I>*>(pEngineChannel);
681                  Pool< Note<V> >* pNotePool = GetNotePool();                  Pool< Note<V> >* pNotePool = GetNotePool();
682    
# Line 689  namespace LinuxSampler { Line 690  namespace LinuxSampler {
690                  NoteIterator itNewNote = pNotePool->allocAppend();                  NoteIterator itNewNote = pNotePool->allocAppend();
691                  const note_id_t newNoteID = pNotePool->getID(itNewNote);                  const note_id_t newNoteID = pNotePool->getID(itNewNote);
692    
693                    // remember the engine's time when this note was triggered exactly
694                    itNewNote->triggerSchedTime = itNoteOnEvent->SchedTime();
695    
696                  // usually the new note (and its subsequent voices) will be                  // usually the new note (and its subsequent voices) will be
697                  // allocated on the key provided by the event's note number,                  // allocated on the key provided by the event's note number,
698                  // however if this new note is requested not to be a regular                  // however if this new note is requested not to be a regular
699                  // note, but rather a child note, then this new note will be                  // note, but rather a child note, then this new note will be
700                  // allocated on the parent note's key instead in order to                  // allocated on the parent note's key instead in order to
701                  // release the child note simultaniously with its parent note                  // release the child note simultaniously with its parent note
702                  itNewNote->hostKey = pNoteOnEvent->Param.Note.Key;                  itNewNote->hostKey = itNoteOnEvent->Param.Note.Key;
703    
704                  // in case this new note was requested to be a child note,                  // in case this new note was requested to be a child note,
705                  // then retrieve its parent note and link them with each other                  // then retrieve its parent note and link them with each other
706                  const note_id_t parentNoteID = pNoteOnEvent->Param.Note.ParentNoteID;                  const note_id_t parentNoteID = itNoteOnEvent->Param.Note.ParentNoteID;
707                  if (parentNoteID) {                  if (parentNoteID) {
708                      NoteIterator itParentNote = pNotePool->fromID(parentNoteID);                                              NoteIterator itParentNote = pNotePool->fromID(parentNoteID);                        
709                      if (itParentNote) {                      if (itParentNote) {
# Line 727  namespace LinuxSampler { Line 731  namespace LinuxSampler {
731                  dmsg(2,("Launched new note on host key %d\n", itNewNote->hostKey));                  dmsg(2,("Launched new note on host key %d\n", itNewNote->hostKey));
732    
733                  // copy event which caused this note                  // copy event which caused this note
734                  itNewNote->cause = *pNoteOnEvent;                  itNewNote->cause = *itNoteOnEvent;
735                  itNewNote->eventID = pEventPool->getID(pNoteOnEvent);                  itNewNote->eventID = pEventPool->getID(itNoteOnEvent);
736                    if (!itNewNote->eventID) {
737                        dmsg(0,("Engine: No valid event ID resolved for note. This is a bug!!!\n"));
738                    }
739    
740                  // move new note to its host key                  // move new note to its host key
741                  MidiKey* pKey = &pChannel->pMIDIKeyInfo[itNewNote->hostKey];                  MidiKey* pKey = &pChannel->pMIDIKeyInfo[itNewNote->hostKey];
742                  itNewNote.moveToEndOf(pKey->pActiveNotes);                  itNewNote.moveToEndOf(pKey->pActiveNotes);
743    
744                  // assign unique note ID of this new note to the original note on event                  // assign unique note ID of this new note to the original note on event
745                  pNoteOnEvent->Param.Note.ID = newNoteID;                  itNoteOnEvent->Param.Note.ID = newNoteID;
746    
747                  return newNoteID; // success                  return newNoteID; // success
748              }              }
# Line 771  namespace LinuxSampler { Line 778  namespace LinuxSampler {
778                      //                      //
779                      // FIXME: it would probably be better to just schedule newly spawned script executions here and then execute them altogether with already suspended ones all at once in order of all their scheduled timing                      // FIXME: it would probably be better to just schedule newly spawned script executions here and then execute them altogether with already suspended ones all at once in order of all their scheduled timing
780                      for (RTList<Event>::Iterator itEvent = pChannel->pEvents->first(),                      for (RTList<Event>::Iterator itEvent = pChannel->pEvents->first(),
781                          end = pChannel->pEvents->end(); itEvent != end; ++itEvent)                          end = pChannel->pEvents->end(); itEvent != end; )
782                      {                      {
783                            //HACK: avoids iterator invalidation which might happen below since an instrument script might drop an event by direct raw pointer access (it would be considerable to extend the Iterator class to detect and circumvent this case by checking the "reincarnation" member variable).
784                            RTList<Event>::Iterator itNext = itEvent;
785                            ++itNext;
786    
787                          switch (itEvent->Type) {                          switch (itEvent->Type) {
788                              case Event::type_note_on:                              case Event::type_note_on:
789                                  if (pChannel->pScript->handlerNote)                                  if (pChannel->pScript->handlerNote)
# Line 791  namespace LinuxSampler { Line 802  namespace LinuxSampler {
802                              case Event::type_note_pressure:                              case Event::type_note_pressure:
803                                  //TODO: ...                                  //TODO: ...
804                                  break;                                  break;
805    
806                                case Event::type_sysex:
807                                    //TODO: ...
808                                    break;
809    
810                                case Event::type_cancel_release_key:
811                                case Event::type_release_key:
812                                case Event::type_release_note:
813                                case Event::type_play_note:
814                                case Event::type_stop_note:
815                                case Event::type_kill_note:
816                                case Event::type_note_synth_param:
817                                    break; // noop
818                          }                          }
819    
820                            // see HACK comment above
821                            itEvent = itNext;
822                      }                      }
823    
824                      // this has to be run again, since the newly spawned scripts                      // this has to be run again, since the newly spawned scripts
# Line 846  namespace LinuxSampler { Line 873  namespace LinuxSampler {
873                                  dmsg(5,("Engine: Note on received\n"));                                  dmsg(5,("Engine: Note on received\n"));
874                                  ProcessNoteOn((EngineChannel*)itEvent->pEngineChannel, itEvent);                                  ProcessNoteOn((EngineChannel*)itEvent->pEngineChannel, itEvent);
875                                  break;                                  break;
876                                case Event::type_play_note:
877                                    dmsg(5,("Engine: Play Note received\n"));
878                                    ProcessNoteOn((EngineChannel*)itEvent->pEngineChannel, itEvent);
879                                    break;
880                              case Event::type_note_off:                              case Event::type_note_off:
881                                  dmsg(5,("Engine: Note off received\n"));                                  dmsg(5,("Engine: Note off received\n"));
882                                  ProcessNoteOff((EngineChannel*)itEvent->pEngineChannel, itEvent);                                  ProcessNoteOff((EngineChannel*)itEvent->pEngineChannel, itEvent);
883                                  break;                                  break;
884                                case Event::type_stop_note:
885                                    dmsg(5,("Engine: Stop Note received\n"));
886                                    ProcessNoteOff((EngineChannel*)itEvent->pEngineChannel, itEvent);
887                                    break;
888                                case Event::type_kill_note:
889                                    dmsg(5,("Engine: Kill Note received\n"));
890                                    ProcessKillNote((EngineChannel*)itEvent->pEngineChannel, itEvent);
891                                    break;
892                              case Event::type_control_change:                              case Event::type_control_change:
893                                  dmsg(5,("Engine: MIDI CC received\n"));                                  dmsg(5,("Engine: MIDI CC received\n"));
894                                  ProcessControlChange((EngineChannel*)itEvent->pEngineChannel, itEvent);                                  ProcessControlChange((EngineChannel*)itEvent->pEngineChannel, itEvent);
# Line 866  namespace LinuxSampler { Line 905  namespace LinuxSampler {
905                                  dmsg(5,("Engine: Pitchbend received\n"));                                  dmsg(5,("Engine: Pitchbend received\n"));
906                                  ProcessPitchbend(static_cast<AbstractEngineChannel*>(itEvent->pEngineChannel), itEvent);                                  ProcessPitchbend(static_cast<AbstractEngineChannel*>(itEvent->pEngineChannel), itEvent);
907                                  break;                                  break;
908                                case Event::type_note_synth_param:
909                                    dmsg(5,("Engine: Note Synth Param received\n"));
910                                    ProcessNoteSynthParam(itEvent->pEngineChannel, itEvent);
911                                    break;
912                                case Event::type_sysex:
913                                    break; // TODO ...
914    
915                                case Event::type_cancel_release_key:
916                                case Event::type_release_key:
917                                case Event::type_release_note:
918                                    break; // noop
919                          }                          }
920                      }                      }
921                  }                  }
# Line 934  namespace LinuxSampler { Line 984  namespace LinuxSampler {
984                      // script event object                      // script event object
985                      RTList<ScriptEvent>::Iterator itScriptEvent =                      RTList<ScriptEvent>::Iterator itScriptEvent =
986                          pChannel->pScript->pEvents->allocAppend();                          pChannel->pScript->pEvents->allocAppend();
987                        // if event handler uses polyphonic variables, reset them
988                        // to zero values before starting to execute the handler
989                        if (pEventHandler->isPolyphonic())
990                            itScriptEvent->execCtx->resetPolyphonicData();
991                      ProcessScriptEvent(                      ProcessScriptEvent(
992                          pChannel, itEvent, pEventHandler, itScriptEvent                          pChannel, itEvent, pEventHandler, itScriptEvent
993                      );                      );
# Line 966  namespace LinuxSampler { Line 1020  namespace LinuxSampler {
1020    
1021                  // initialize/reset other members                  // initialize/reset other members
1022                  itScriptEvent->cause = *itEvent;                  itScriptEvent->cause = *itEvent;
1023                    itScriptEvent->scheduleTime = itEvent->SchedTime();
1024                  itScriptEvent->currentHandler = 0;                  itScriptEvent->currentHandler = 0;
1025                  itScriptEvent->executionSlices = 0;                  itScriptEvent->executionSlices = 0;
1026                    itScriptEvent->ignoreAllWaitCalls = false;
1027                    itScriptEvent->handlerType = pEventHandler->eventHandlerType();
1028                  // this is the native representation of the $EVENT_ID script variable                  // this is the native representation of the $EVENT_ID script variable
1029                  itScriptEvent->id =                  itScriptEvent->id =
1030                      (itEvent->Type == Event::type_note_on)                      (itEvent->Type == Event::type_note_on)
# Line 1235  namespace LinuxSampler { Line 1292  namespace LinuxSampler {
1292                          // the script's "init" event handler is only executed                          // the script's "init" event handler is only executed
1293                          // once (when the script is loaded or reloaded)                          // once (when the script is loaded or reloaded)
1294                          if (pEngineChannel->pScript && pEngineChannel->pScript->handlerInit) {                          if (pEngineChannel->pScript && pEngineChannel->pScript->handlerInit) {
1295                                dmsg(5,("Engine: exec handlerInit %p\n", pEngineChannel->pScript->handlerInit));
1296                              RTList<ScriptEvent>::Iterator itScriptEvent =                              RTList<ScriptEvent>::Iterator itScriptEvent =
1297                                  pEngineChannel->pScript->pEvents->allocAppend();                                  pEngineChannel->pScript->pEvents->allocAppend();
1298    
1299                              itScriptEvent->cause.pEngineChannel = pEngineChannel;                              itScriptEvent->cause.pEngineChannel = pEngineChannel;
1300                              itScriptEvent->handlers[0] = pEngineChannel->pScript->handlerInit;                              itScriptEvent->handlers[0] = pEngineChannel->pScript->handlerInit;
1301                              itScriptEvent->handlers[1] = NULL;                              itScriptEvent->handlers[1] = NULL;
1302                                itScriptEvent->currentHandler = 0;
1303                                itScriptEvent->executionSlices = 0;
1304                                itScriptEvent->ignoreAllWaitCalls = false;
1305                                itScriptEvent->handlerType = VM_EVENT_HANDLER_INIT;
1306    
1307                              VMExecStatus_t res = pScriptVM->exec(                              /*VMExecStatus_t res = */ pScriptVM->exec(
1308                                  pEngineChannel->pScript->parserContext, &*itScriptEvent                                  pEngineChannel->pScript->parserContext, &*itScriptEvent
1309                              );                              );
1310    
# Line 1305  namespace LinuxSampler { Line 1367  namespace LinuxSampler {
1367                          // usually there should already be a new Note object                          // usually there should already be a new Note object
1368                          NoteIterator itNote = GetNotePool()->fromID(itVoiceStealEvent->Param.Note.ID);                          NoteIterator itNote = GetNotePool()->fromID(itVoiceStealEvent->Param.Note.ID);
1369                          if (!itNote) { // should not happen, but just to be sure ...                          if (!itNote) { // should not happen, but just to be sure ...
1370                              const note_id_t noteID = LaunchNewNote(pEngineChannel, &*itVoiceStealEvent);                              const note_id_t noteID = LaunchNewNote(pEngineChannel, itVoiceStealEvent);
1371                              if (!noteID) {                              if (!noteID) {
1372                                  dmsg(1,("Engine: Voice stealing failed; No Note object and Note pool empty!\n"));                                  dmsg(1,("Engine: Voice stealing failed; No Note object and Note pool empty!\n"));
1373                                  continue;                                  continue;
# Line 1582  namespace LinuxSampler { Line 1644  namespace LinuxSampler {
1644               *  @param pEngineChannel - engine channel on which this event occurred on               *  @param pEngineChannel - engine channel on which this event occurred on
1645               *  @param itNoteOnEvent - key, velocity and time stamp of the event               *  @param itNoteOnEvent - key, velocity and time stamp of the event
1646               */               */
1647              virtual void ProcessNoteOn(EngineChannel* pEngineChannel, Pool<Event>::Iterator& itNoteOnEvent) {              virtual void ProcessNoteOn(EngineChannel* pEngineChannel, Pool<Event>::Iterator& itNoteOnEvent) OVERRIDE {
1648                  EngineChannelBase<V, R, I>* pChannel =                  EngineChannelBase<V, R, I>* pChannel =
1649                          static_cast<EngineChannelBase<V, R, I>*>(pEngineChannel);                          static_cast<EngineChannelBase<V, R, I>*>(pEngineChannel);
1650    
# Line 1592  namespace LinuxSampler { Line 1654  namespace LinuxSampler {
1654    
1655                  MidiKey* pKey = &pChannel->pMIDIKeyInfo[key];                  MidiKey* pKey = &pChannel->pMIDIKeyInfo[key];
1656    
1657                  pChannel->listeners.PreProcessNoteOn(key, vel);                  // There are real MIDI note-on events (Event::type_note_on) and
1658                    // programmatically spawned notes (Event::type_play_note). We have
1659                    // to distinguish between them, since certain processing below
1660                    // must only be done on real MIDI note-on events (i.e. for
1661                    // correctly updating which MIDI keys are currently pressed down).
1662                    const bool isRealMIDINoteOnEvent = itNoteOnEvent->Type == Event::type_note_on;
1663    
1664                    if (isRealMIDINoteOnEvent)
1665                        pChannel->listeners.PreProcessNoteOn(key, vel);
1666    
1667                  #if !CONFIG_PROCESS_MUTED_CHANNELS                  #if !CONFIG_PROCESS_MUTED_CHANNELS
1668                  if (pEngineChannel->GetMute()) { // skip if sampler channel is muted                  if (pEngineChannel->GetMute()) { // skip if sampler channel is muted
1669                      pChannel->listeners.PostProcessNoteOn(key, vel);                      if (isRealMIDINoteOnEvent)
1670                            pChannel->listeners.PostProcessNoteOn(key, vel);
1671                      return;                      return;
1672                  }                  }
1673                  #endif                  #endif
1674    
1675                  if (!pChannel->pInstrument) {                  if (!pChannel->pInstrument) {
1676                      pChannel->listeners.PostProcessNoteOn(key, vel);                      if (isRealMIDINoteOnEvent)
1677                            pChannel->listeners.PostProcessNoteOn(key, vel);
1678                      return; // ignore if no instrument loaded                      return; // ignore if no instrument loaded
1679                  }                  }
1680    
# Line 1609  namespace LinuxSampler { Line 1682  namespace LinuxSampler {
1682                  RTList<Event>::Iterator itNoteOnEventOnKeyList = itNoteOnEvent.moveToEndOf(pKey->pEvents);                  RTList<Event>::Iterator itNoteOnEventOnKeyList = itNoteOnEvent.moveToEndOf(pKey->pEvents);
1683    
1684                  // if Solo Mode then kill all already active voices                  // if Solo Mode then kill all already active voices
1685                  if (pChannel->SoloMode) {                  if (pChannel->SoloMode && isRealMIDINoteOnEvent) {
1686                      Pool<uint>::Iterator itYoungestKey = pChannel->pActiveKeys->last();                      Pool<uint>::Iterator itYoungestKey = pChannel->pActiveKeys->last();
1687                      if (itYoungestKey) {                      if (itYoungestKey) {
1688                          const int iYoungestKey = *itYoungestKey;                          const int iYoungestKey = *itYoungestKey;
# Line 1638  namespace LinuxSampler { Line 1711  namespace LinuxSampler {
1711                      pChannel->SoloKey = key;                      pChannel->SoloKey = key;
1712                  }                  }
1713    
1714                  pChannel->ProcessKeySwitchChange(key);                  if (isRealMIDINoteOnEvent) {
1715                        pChannel->ProcessKeySwitchChange(key);
1716    
1717                  pKey->KeyPressed = true; // the MIDI key was now pressed down                      pKey->KeyPressed = true; // the MIDI key was now pressed down
1718                  pChannel->KeyDown[key] = true; // just used as built-in %KEY_DOWN script variable                      pChannel->KeyDown[key] = true; // just used as built-in %KEY_DOWN script variable
1719                  pKey->Velocity   = itNoteOnEventOnKeyList->Param.Note.Velocity;                      pKey->Velocity   = itNoteOnEventOnKeyList->Param.Note.Velocity;
1720                  pKey->NoteOnTime = FrameTime + itNoteOnEventOnKeyList->FragmentPos(); // will be used to calculate note length                      pKey->NoteOnTime = FrameTime + itNoteOnEventOnKeyList->FragmentPos(); // will be used to calculate note length
1721                    }
1722    
1723                  // cancel release process of voices on this key if needed                  // cancel release process of voices on this key if needed
1724                  if (pKey->Active && !pChannel->SustainPedal) {                  if (pKey->Active && !pChannel->SustainPedal && isRealMIDINoteOnEvent) {
1725                      RTList<Event>::Iterator itCancelReleaseEvent = pKey->pEvents->allocAppend();                      RTList<Event>::Iterator itCancelReleaseEvent = pKey->pEvents->allocAppend();
1726                      if (itCancelReleaseEvent) {                      if (itCancelReleaseEvent) {
1727                          *itCancelReleaseEvent = *itNoteOnEventOnKeyList;         // copy event                          *itCancelReleaseEvent = *itNoteOnEventOnKeyList;         // copy event
1728                          itCancelReleaseEvent->Type = Event::type_cancel_release; // transform event type                          itCancelReleaseEvent->Type = Event::type_cancel_release_key; // transform event type
1729                      }                      }
1730                      else dmsg(1,("Event pool emtpy!\n"));                      else dmsg(1,("Event pool emtpy!\n"));
1731                  }                  }
# Line 1661  namespace LinuxSampler { Line 1736  namespace LinuxSampler {
1736                  if (!pKey->Active && !pKey->VoiceTheftsQueued)                  if (!pKey->Active && !pKey->VoiceTheftsQueued)
1737                      pKey->pEvents->free(itNoteOnEventOnKeyList);                      pKey->pEvents->free(itNoteOnEventOnKeyList);
1738    
1739                  if (!pChannel->SoloMode || pChannel->PortamentoPos < 0.0f) pChannel->PortamentoPos = (float) key;                  if (isRealMIDINoteOnEvent && (!pChannel->SoloMode || pChannel->PortamentoPos < 0.0f))
1740                        pChannel->PortamentoPos = (float) key;
1741    
1742                    //NOTE: Hmm, I guess its a matter of taste whether round robin should be advanced only on real MIDI note-on events, isn't it?
1743                  if (pKey->pRoundRobinIndex) {                  if (pKey->pRoundRobinIndex) {
1744                      (*pKey->pRoundRobinIndex)++; // counter specific for the key or region                      (*pKey->pRoundRobinIndex)++; // counter specific for the key or region
1745                      pChannel->RoundRobinIndex++; // common counter for the channel                      pChannel->RoundRobinIndex++; // common counter for the channel
1746                  }                  }
1747                  pChannel->listeners.PostProcessNoteOn(key, vel);  
1748                    if (isRealMIDINoteOnEvent)
1749                        pChannel->listeners.PostProcessNoteOn(key, vel);
1750              }              }
1751    
1752              /**              /**
# Line 1695  namespace LinuxSampler { Line 1775  namespace LinuxSampler {
1775               *  @param pEngineChannel - engine channel on which this event occurred on               *  @param pEngineChannel - engine channel on which this event occurred on
1776               *  @param itNoteOffEvent - key, velocity and time stamp of the event               *  @param itNoteOffEvent - key, velocity and time stamp of the event
1777               */               */
1778              virtual void ProcessNoteOff(EngineChannel* pEngineChannel, Pool<Event>::Iterator& itNoteOffEvent) {              virtual void ProcessNoteOff(EngineChannel* pEngineChannel, Pool<Event>::Iterator& itNoteOffEvent) OVERRIDE {
1779                  EngineChannelBase<V, R, I>* pChannel = static_cast<EngineChannelBase<V, R, I>*>(pEngineChannel);                  EngineChannelBase<V, R, I>* pChannel = static_cast<EngineChannelBase<V, R, I>*>(pEngineChannel);
1780    
1781                  const int iKey = itNoteOffEvent->Param.Note.Key;                  const int iKey = itNoteOffEvent->Param.Note.Key;
# Line 1704  namespace LinuxSampler { Line 1784  namespace LinuxSampler {
1784    
1785                  MidiKey* pKey = &pChannel->pMIDIKeyInfo[iKey];                  MidiKey* pKey = &pChannel->pMIDIKeyInfo[iKey];
1786    
1787                  pChannel->listeners.PreProcessNoteOff(iKey, vel);                  // There are real MIDI note-off events (Event::type_note_off) and
1788                    // programmatically spawned notes (Event::type_stop_note). We have
1789                    // to distinguish between them, since certain processing below
1790                    // must only be done on real MIDI note-off events (i.e. for
1791                    // correctly updating which MIDI keys are currently pressed down),
1792                    // plus a stop-note event just releases voices of one particular
1793                    // note, whereas a note-off event releases all voices on a
1794                    // particular MIDI key instead.
1795                    const bool isRealMIDINoteOffEvent = itNoteOffEvent->Type == Event::type_note_off;
1796    
1797                    if (isRealMIDINoteOffEvent)
1798                        pChannel->listeners.PreProcessNoteOff(iKey, vel);
1799    
1800                  #if !CONFIG_PROCESS_MUTED_CHANNELS                  #if !CONFIG_PROCESS_MUTED_CHANNELS
1801                  if (pEngineChannel->GetMute()) { // skip if sampler channel is muted                  if (pEngineChannel->GetMute()) { // skip if sampler channel is muted
1802                      pChannel->listeners.PostProcessNoteOff(iKey, vel);                      if (isRealMIDINoteOffEvent)
1803                            pChannel->listeners.PostProcessNoteOff(iKey, vel);
1804                      return;                      return;
1805                  }                  }
1806                  #endif                  #endif
1807    
1808                  pKey->KeyPressed = false; // the MIDI key was now released                  if (isRealMIDINoteOffEvent) {
1809                  pChannel->KeyDown[iKey] = false; // just used as built-in %KEY_DOWN script variable                      pKey->KeyPressed = false; // the MIDI key was now released
1810                        pChannel->KeyDown[iKey] = false; // just used as built-in %KEY_DOWN script variable
1811                    }
1812    
1813                  // move event to the key's own event list                  // move event to the key's own event list
1814                  RTList<Event>::Iterator itNoteOffEventOnKeyList = itNoteOffEvent.moveToEndOf(pKey->pEvents);                  RTList<Event>::Iterator itNoteOffEventOnKeyList = itNoteOffEvent.moveToEndOf(pKey->pEvents);
1815    
1816                  bool bShouldRelease = pKey->Active && pChannel->ShouldReleaseVoice(itNoteOffEventOnKeyList->Param.Note.Key);                  if (isRealMIDINoteOffEvent) {
1817                        bool bShouldRelease = pKey->Active && pChannel->ShouldReleaseVoice(itNoteOffEventOnKeyList->Param.Note.Key);
1818    
1819                  // in case Solo Mode is enabled, kill all voices on this key and respawn a voice on the highest pressed key (if any)                      // in case Solo Mode is enabled, kill all voices on this key and respawn a voice on the highest pressed key (if any)
1820                  if (pChannel->SoloMode && pChannel->pInstrument) { //TODO: this feels like too much code just for handling solo mode :P                      if (pChannel->SoloMode && pChannel->pInstrument) { //TODO: this feels like too much code just for handling solo mode :P
1821                      bool bOtherKeysPressed = false;                          bool bOtherKeysPressed = false;
1822                      if (iKey == pChannel->SoloKey) {                          if (iKey == pChannel->SoloKey) {
1823                          pChannel->SoloKey = -1;                              pChannel->SoloKey = -1;
1824                          // if there's still a key pressed down, respawn a voice (group) on the highest key                              // if there's still a key pressed down, respawn a voice (group) on the highest key
1825                          for (int i = 127; i > 0; i--) {                              for (int i = 127; i > 0; i--) {
1826                              MidiKey* pOtherKey = &pChannel->pMIDIKeyInfo[i];                                  MidiKey* pOtherKey = &pChannel->pMIDIKeyInfo[i];
1827                              if (pOtherKey->KeyPressed) {                                  if (pOtherKey->KeyPressed) {
1828                                  bOtherKeysPressed = true;                                      bOtherKeysPressed = true;
1829                                  // make the other key the new 'currently active solo key'                                      // make the other key the new 'currently active solo key'
1830                                  pChannel->SoloKey = i;                                      pChannel->SoloKey = i;
1831                                  // get final portamento position of currently active voice                                      // get final portamento position of currently active voice
1832                                  if (pChannel->PortamentoMode) {                                      if (pChannel->PortamentoMode) {
1833                                      NoteIterator itNote = pKey->pActiveNotes->first();                                          NoteIterator itNote = pKey->pActiveNotes->first();
1834                                      VoiceIterator itVoice = itNote->pActiveVoices->first();                                          VoiceIterator itVoice = itNote->pActiveVoices->first();
1835                                      if (itVoice) itVoice->UpdatePortamentoPos(itNoteOffEventOnKeyList);                                          if (itVoice) itVoice->UpdatePortamentoPos(itNoteOffEventOnKeyList);
                                 }  
                                 // create a pseudo note on event  
                                 RTList<Event>::Iterator itPseudoNoteOnEvent = pOtherKey->pEvents->allocAppend();  
                                 if (itPseudoNoteOnEvent) {  
                                     // copy event  
                                     *itPseudoNoteOnEvent = *itNoteOffEventOnKeyList;  
                                     // transform event to a note on event  
                                     itPseudoNoteOnEvent->Type                = Event::type_note_on;  
                                     itPseudoNoteOnEvent->Param.Note.Key      = i;  
                                     itPseudoNoteOnEvent->Param.Note.Velocity = pOtherKey->Velocity;  
                                     // assign a new note to this note-on event  
                                     if (LaunchNewNote(pChannel, &*itPseudoNoteOnEvent)) {  
                                         // allocate and trigger new voice(s) for the other key  
                                         TriggerNewVoices(pChannel, itPseudoNoteOnEvent, false);  
1836                                      }                                      }
1837                                      // if neither a voice was spawned or postponed then remove note on event from key again                                      // create a pseudo note on event
1838                                      if (!pOtherKey->Active && !pOtherKey->VoiceTheftsQueued)                                      RTList<Event>::Iterator itPseudoNoteOnEvent = pOtherKey->pEvents->allocAppend();
1839                                          pOtherKey->pEvents->free(itPseudoNoteOnEvent);                                      if (itPseudoNoteOnEvent) {
1840                                            // copy event
1841                                            *itPseudoNoteOnEvent = *itNoteOffEventOnKeyList;
1842                                            // transform event to a note on event
1843                                            itPseudoNoteOnEvent->Type                = Event::type_note_on; //FIXME: should probably use Event::type_play_note instead (to avoid i.e. hanging notes)
1844                                            itPseudoNoteOnEvent->Param.Note.Key      = i;
1845                                            itPseudoNoteOnEvent->Param.Note.Velocity = pOtherKey->Velocity;
1846                                            // assign a new note to this note-on event
1847                                            if (LaunchNewNote(pChannel, itPseudoNoteOnEvent)) {
1848                                                // allocate and trigger new voice(s) for the other key
1849                                                TriggerNewVoices(pChannel, itPseudoNoteOnEvent, false);
1850                                            }
1851                                            // if neither a voice was spawned or postponed then remove note on event from key again
1852                                            if (!pOtherKey->Active && !pOtherKey->VoiceTheftsQueued)
1853                                                pOtherKey->pEvents->free(itPseudoNoteOnEvent);
1854    
1855                                  } else dmsg(1,("Could not respawn voice, no free event left\n"));                                      } else dmsg(1,("Could not respawn voice, no free event left\n"));
1856                                  break; // done                                      break; // done
1857                                    }
1858                              }                              }
1859                          }                          }
1860                      }                          if (bOtherKeysPressed) {
1861                      if (bOtherKeysPressed) {                              if (pKey->Active) { // kill all voices on this key
1862                          if (pKey->Active) { // kill all voices on this key                                  bShouldRelease = false; // no need to release, as we kill it here
1863                              bShouldRelease = false; // no need to release, as we kill it here                                  for (NoteIterator itNote = pKey->pActiveNotes->first(); itNote; ++itNote) {
1864                              for (NoteIterator itNote = pKey->pActiveNotes->first(); itNote; ++itNote) {                                      VoiceIterator itVoiceToBeKilled = itNote->pActiveVoices->first();
1865                                  VoiceIterator itVoiceToBeKilled = itNote->pActiveVoices->first();                                      VoiceIterator end               = itNote->pActiveVoices->end();
1866                                  VoiceIterator end               = itNote->pActiveVoices->end();                                      for (; itVoiceToBeKilled != end; ++itVoiceToBeKilled) {
1867                                  for (; itVoiceToBeKilled != end; ++itVoiceToBeKilled) {                                          if (!(itVoiceToBeKilled->Type & Voice::type_release_trigger))
1868                                      if (!(itVoiceToBeKilled->Type & Voice::type_release_trigger))                                              itVoiceToBeKilled->Kill(itNoteOffEventOnKeyList);
1869                                          itVoiceToBeKilled->Kill(itNoteOffEventOnKeyList);                                      }
1870                                  }                                  }
1871                              }                              }
1872                          }                          } else pChannel->PortamentoPos = -1.0f;
1873                      } else pChannel->PortamentoPos = -1.0f;                      }
                 }  
1874    
1875                  // if no solo mode (the usual case) or if solo mode and no other key pressed, then release voices on this key if needed                      // if no solo mode (the usual case) or if solo mode and no other key pressed, then release voices on this key if needed
1876                  if (bShouldRelease) {                      if (bShouldRelease) {
1877                      itNoteOffEventOnKeyList->Type = Event::type_release; // transform event type                          itNoteOffEventOnKeyList->Type = Event::type_release_key; // transform event type
1878                            // spawn release triggered voice(s) if needed
1879                      // spawn release triggered voice(s) if needed                          ProcessReleaseTrigger(pChannel, itNoteOffEventOnKeyList, pKey);
1880                      if (pKey->ReleaseTrigger && pChannel->pInstrument) {                      }
1881                          // assign a new note to this release event                  } else if (itNoteOffEventOnKeyList->Type == Event::type_stop_note) {
1882                          if (LaunchNewNote(pChannel, &*itNoteOffEventOnKeyList)) {                      // This programmatically caused event is caused by a call to
1883                              // allocate and trigger new release voice(s)                      // the built-in instrument script function note_off(). In
1884                              TriggerReleaseVoices(pChannel, itNoteOffEventOnKeyList);                      // contrast to a real MIDI note-off event the stop-note
1885                          }                      // event just intends to release voices of one particular note.
1886                          pKey->ReleaseTrigger = false;                      NoteBase* pNote = pChannel->pEngine->NoteByID( itNoteOffEventOnKeyList->Param.Note.ID );
1887                        if (pNote) { // the requested note is still alive ...
1888                            itNoteOffEventOnKeyList->Type = Event::type_release_note; // transform event type
1889                        } else { // note is dead and gone ..
1890                            pKey->pEvents->free(itNoteOffEventOnKeyList); // remove stop-note event from key again
1891                            return; // prevent event to be removed a 2nd time below
1892                      }                      }
1893                  }                  }
1894    
# Line 1796  namespace LinuxSampler { Line 1896  namespace LinuxSampler {
1896                  if (!pKey->Active && !pKey->VoiceTheftsQueued)                  if (!pKey->Active && !pKey->VoiceTheftsQueued)
1897                      pKey->pEvents->free(itNoteOffEventOnKeyList);                      pKey->pEvents->free(itNoteOffEventOnKeyList);
1898    
1899                  pChannel->listeners.PostProcessNoteOff(iKey, vel);                  if (isRealMIDINoteOffEvent)
1900                        pChannel->listeners.PostProcessNoteOff(iKey, vel);
1901                }
1902    
1903                /**
1904                 * Called on sustain pedal up events to check and if required,
1905                 * launch release trigger voices on the respective active key.
1906                 *
1907                 * @param pEngineChannel - engine channel on which this event occurred on
1908                 * @param itEvent - release trigger event (contains note number)
1909                 */
1910                virtual void ProcessReleaseTrigger(EngineChannel* pEngineChannel, RTList<Event>::Iterator& itEvent) OVERRIDE {
1911                    EngineChannelBase<V, R, I>* pChannel = static_cast<EngineChannelBase<V, R, I>*>(pEngineChannel);
1912    
1913                    const int iKey = itEvent->Param.Note.Key;
1914                    if (iKey < 0 || iKey > 127) return; // ignore event, key outside allowed key range
1915    
1916                    MidiKey* pKey = &pChannel->pMIDIKeyInfo[iKey];
1917    
1918                    ProcessReleaseTrigger(pChannel, itEvent, pKey);
1919                }
1920    
1921                /**
1922                 * Called on note-off and sustain pedal up events to check and if
1923                 * required, launch release trigger voices on the respective active
1924                 * key.
1925                 *
1926                 * @param pEngineChannel - engine channel on which this event occurred on
1927                 * @param itEvent - note off event / release trigger event
1928                 * @param pKey - key on which the release trigger voices shall be spawned
1929                 */
1930                inline void ProcessReleaseTrigger(EngineChannelBase<V, R, I>* pChannel, RTList<Event>::Iterator& itEvent, MidiKey* pKey) {
1931                    // spawn release triggered voice(s) if needed
1932                    if (pKey->ReleaseTrigger && pChannel->pInstrument) {
1933                        // assign a new note to this release event
1934                        if (LaunchNewNote(pChannel, itEvent)) {
1935                            // allocate and trigger new release voice(s)
1936                            TriggerReleaseVoices(pChannel, itEvent);
1937                        }
1938                        pKey->ReleaseTrigger = false;
1939                    }
1940                }
1941    
1942                /**
1943                 * Called on "kill note" events, which currently only happens on
1944                 * built-in real-time instrument script function fade_out(). This
1945                 * method only fulfills one task: moving the even to the Note's own
1946                 * event list so that its voices can process the kill event sample
1947                 * accurately.
1948                 */
1949                void ProcessKillNote(EngineChannel* pEngineChannel, RTList<Event>::Iterator& itEvent) {
1950                    EngineChannelBase<V, R, I>* pChannel = static_cast<EngineChannelBase<V, R, I>*>(pEngineChannel);
1951    
1952                    NoteBase* pNote = pChannel->pEngine->NoteByID( itEvent->Param.Note.ID );
1953                    if (!pNote || pNote->hostKey < 0 || pNote->hostKey >= 128) return;
1954    
1955                    // move note kill event to its MIDI key
1956                    MidiKey* pKey = &pChannel->pMIDIKeyInfo[pNote->hostKey];
1957                    itEvent.moveToEndOf(pKey->pEvents);
1958                }
1959    
1960                /**
1961                 * Called on note synthesis parameter change events. These are
1962                 * internal events caused by calling built-in real-time instrument
1963                 * script functions like change_vol(), change_tune(), etc.
1964                 *
1965                 * This method performs two tasks:
1966                 *
1967                 * - It converts the event's relative values changes (Deltas) to
1968                 *   the respective final new synthesis parameter value (AbsValue),
1969                 *   for that particular moment of the event that is.
1970                 *
1971                 * - It moves the individual events to the Note's own event list
1972                 *   (or actually to the event list of the MIDI key), so that
1973                 *   voices can process those events sample accurately.
1974                 *
1975                 * @param pEngineChannel - engine channel on which this event occurred on
1976                 * @param itEvent - note synthesis parameter change event
1977                 */
1978                virtual void ProcessNoteSynthParam(EngineChannel* pEngineChannel, RTList<Event>::Iterator& itEvent) {
1979                    EngineChannelBase<V, R, I>* pChannel = static_cast<EngineChannelBase<V, R, I>*>(pEngineChannel);
1980    
1981                    NoteBase* pNote = pChannel->pEngine->NoteByID( itEvent->Param.NoteSynthParam.NoteID );
1982                    if (!pNote || pNote->hostKey < 0 || pNote->hostKey >= 128) return;
1983    
1984                    const bool& relative = itEvent->Param.NoteSynthParam.Relative;
1985    
1986                    switch (itEvent->Param.NoteSynthParam.Type) {
1987                        case Event::synth_param_volume:
1988                            if (relative)
1989                                pNote->Override.Volume *= itEvent->Param.NoteSynthParam.Delta;
1990                            else
1991                                pNote->Override.Volume = itEvent->Param.NoteSynthParam.Delta;
1992                            itEvent->Param.NoteSynthParam.AbsValue = pNote->Override.Volume;
1993                            break;
1994                        case Event::synth_param_volume_time:
1995                            pNote->Override.VolumeTime = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta;
1996                            break;
1997                        case Event::synth_param_pitch:
1998                            if (relative)
1999                                pNote->Override.Pitch *= itEvent->Param.NoteSynthParam.Delta;
2000                            else
2001                                pNote->Override.Pitch = itEvent->Param.NoteSynthParam.Delta;
2002                            itEvent->Param.NoteSynthParam.AbsValue = pNote->Override.Pitch;
2003                            break;
2004                        case Event::synth_param_pitch_time:
2005                            pNote->Override.PitchTime = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta;
2006                            break;
2007                        case Event::synth_param_pan:
2008                            if (relative) {
2009                                pNote->Override.Pan = RTMath::RelativeSummedAvg(pNote->Override.Pan, itEvent->Param.NoteSynthParam.Delta, ++pNote->Override.PanSources);
2010                            } else {
2011                                pNote->Override.Pan = itEvent->Param.NoteSynthParam.Delta;
2012                                pNote->Override.PanSources = 1; // only relevant on subsequent change_pan() instrument script calls on same note with 'relative' argument being set
2013                            }
2014                            itEvent->Param.NoteSynthParam.AbsValue = pNote->Override.Pan;
2015                            break;
2016                        case Event::synth_param_cutoff:
2017                            pNote->Override.Cutoff = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta;
2018                            break;
2019                        case Event::synth_param_resonance:
2020                            pNote->Override.Resonance = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta;
2021                            break;
2022                        case Event::synth_param_attack:
2023                            pNote->Override.Attack = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta;
2024                            break;
2025                        case Event::synth_param_decay:
2026                            pNote->Override.Decay = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta;
2027                            break;
2028                        case Event::synth_param_release:
2029                            pNote->Override.Release = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta;
2030                            break;
2031                        case Event::synth_param_amp_lfo_depth:
2032                            pNote->Override.AmpLFODepth = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta;
2033                            break;
2034                        case Event::synth_param_amp_lfo_freq:
2035                            pNote->Override.AmpLFOFreq = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta;
2036                            break;
2037                        case Event::synth_param_pitch_lfo_depth:
2038                            pNote->Override.PitchLFODepth = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta;
2039                            break;
2040                        case Event::synth_param_pitch_lfo_freq:
2041                            pNote->Override.PitchLFOFreq = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta;
2042                            break;
2043                    }
2044    
2045                    // move note parameter event to its MIDI key
2046                    MidiKey* pKey = &pChannel->pMIDIKeyInfo[pNote->hostKey];
2047                    itEvent.moveToEndOf(pKey->pEvents);
2048              }              }
2049    
2050              /**              /**
2051               *  Reset all voices and disk thread and clear input event queue and all               *  Reset all voices and disk thread and clear input event queue and all
2052               *  control and status variables. This method is protected by a mutex.               *  control and status variables. This method is protected by a mutex.
2053               */               */
2054              virtual void ResetInternal() {              virtual void ResetInternal() OVERRIDE {
2055                  LockGuard lock(ResetInternalMutex);                  LockGuard lock(ResetInternalMutex);
2056    
2057                  // make sure that the engine does not get any sysex messages                  // make sure that the engine does not get any sysex messages
# Line 1862  namespace LinuxSampler { Line 2110  namespace LinuxSampler {
2110               * @param pEngineChannel - engine channel on which all voices should be killed               * @param pEngineChannel - engine channel on which all voices should be killed
2111               * @param itKillEvent    - event which caused this killing of all voices               * @param itKillEvent    - event which caused this killing of all voices
2112               */               */
2113              virtual void KillAllVoices(EngineChannel* pEngineChannel, Pool<Event>::Iterator& itKillEvent) {              virtual void KillAllVoices(EngineChannel* pEngineChannel, Pool<Event>::Iterator& itKillEvent) OVERRIDE {
2114                  EngineChannelBase<V, R, I>* pChannel = static_cast<EngineChannelBase<V, R, I>*>(pEngineChannel);                  EngineChannelBase<V, R, I>* pChannel = static_cast<EngineChannelBase<V, R, I>*>(pEngineChannel);
2115                  int count = pChannel->KillAllVoices(itKillEvent);                  int count = pChannel->KillAllVoices(itKillEvent);
2116                  VoiceSpawnsLeft -= count; //FIXME: just a temporary workaround, we should check the cause in StealVoice() instead                  VoiceSpawnsLeft -= count; //FIXME: just a temporary workaround, we should check the cause in StealVoice() instead
# Line 1897  namespace LinuxSampler { Line 2145  namespace LinuxSampler {
2145                  bool                    HandleKeyGroupConflicts                  bool                    HandleKeyGroupConflicts
2146              ) = 0;              ) = 0;
2147    
2148              virtual int GetMinFadeOutSamples() { return MinFadeOutSamples; }              virtual int GetMinFadeOutSamples() OVERRIDE { return MinFadeOutSamples; }
2149    
2150              int InitNewVoice (              int InitNewVoice (
2151                  EngineChannelBase<V, R, I>*  pChannel,                  EngineChannelBase<V, R, I>*  pChannel,

Legend:
Removed from v.2879  
changed lines
  Added in v.3207

  ViewVC Help
Powered by ViewVC