Introduction à libURBI pour Urbi 1.0

(book compiled from )

Matthieu Nottale

Traduction de l'anglais Antoine (Zelig) HUE

This document is released under the Attribution-NonCommercial-NoDerivs 2.0 Creative Commons licence (http://creativecommons.org/licenses/by-nc-nd/2.0/deed.en).


Table of Contents

1. Introduction
2. Par où commencer
Se connecter
Envoyer des commandes URBI
Envoyer des données binaires.
Envoyer un son
3. Réception
4. Operations synchrones
Lire la valeur d'un périphérique
Recevoir une image
Recevoir un son
5. Fonctions de conversion
6. Tout mettre ensemble: quelques exemples
7. Conseils de programmation
8. Les fonctions de portabilité
A. Copyright

Chapter 1. Introduction

Liburbi-c++ est une librairie conçue pour encapsuler une connection URBI. Elle gère la connection TCP avec le serveur URBI, et la répartition des messages qu'il envoi. La librairie est thread-safe et réentrante.

La librairie est constituée de deux classes C++, UClient et USyncClient, et quelques fonctions utiles.

Nous supposons que le lecteur est un peu au courant de la syntaxe d'URBI.

Chapter 2. Par où commencer

Se connecter

Pour se connecter à un serveur URBI, il suffit simplement de créer une nouvelle instance UClient (ou USyncClient si vous voulez utiliser les fonctions de synchronisation décrites plus bas), en passant comme premier paramètre le nom ou l'adresse du serveur, et en option le port comme deuxième paramètre:

UClient * client = new UClient("myrobot.ensta.fr");

//a wrapper is also available in the urbi namespace:
UClient * client = urbi::connect("myrobot.ensta.fr");

Le créateur démarrera un thread indépendant qui sera à l'écoute de messages provenant du serveur URBI.

Vous pouvez vérifier si la connection a été établie avec succès en appellant la fonction error, qui retourne la valeur zero en cas de succès, ou un code erreur nonzero en cas de défaut.

Envoyer des commandes URBI

La méthode send est la manière la plus simple d'envoyer une commande au serveur URBI. Elle accepte une syntaxe similaire à la fonction printf. Pour envoyer une séquence de commandes sans risque d'avoir un autre thread qui envoi des commandes en même temps, vous pouvez utiliser les méthodes lockSend et unlockSend pour verrouiller puis déverrouiller le buffer (tampon) d'envoi.

int sleeptime = 50;

client->send("motoron;");

client->lockSend(); //send() appelé par d'autres threads sera bloqué 
					//à ce point point jusqu'au prochain appel de unlockSend

for (float val=0; val<=1; val+=0.05)
    client->send("neck.val = %f;wait (%d);", val, sleeptime);

client->unlockSend();

Alternativement, la classe UClient hérite de l'ostreaam, aussi vous pouvez utiliser l'opérateur <<:

 client << "headPan.val = " <<12 << urbi::comma;

Les constantes 'comma', 'semicolon', 'pipe' et 'parallel' sont définis respectivement par ',', ';', '|' and '&' dans le namespace d'urbi.

Une troisième voie possible est d'utiliser la macro URBI, qui utilise la connection par défaut (la première connection créée avec votre programme):

 URBI((
   headPan.val = 12 ,
   echo "coucou" | speaker.play("test.wav") & leds.val = 1
   ));
 //notez l'absence des doubles quotes pour délimiter le code URBI
 //les double-parenthèses sont requises
 URBI() << "headPan.val = " << 12 <<  urbi::semicolon;

La fonction urbi::setDefaultClientUClient *cl) peut être utilisée pour changer le client par défaut.

Envoyer des données binaires.

Pour envoyer des données binaires au robot, la méthode sendBin doit être utilisée. Elle prend comme paramètres le buffer d'envoi et sa taille, et en option une en-tête.

client->sendBin(soundData, soundDataSize,"speaker.val = BIN %d raw 2 16000 16 1;",
soundDataSize);

Envoyer un son

De même vous pouvez utiliser sendBin pour faire jouer un son au robot, une méthode spécifique et efficace a été écrite pour cette utilisation: sendSound.

client->sendSound(sound, "endsound");

Le premier paramètre est une structure USound décrivant le son a envoyer. Le second est un tag qui sera utilisé par le serveur pour signifier un message sytème "stop" quand le son aura fini d'être joué. La fonction convert peut être utilisée pour convertir entre différents formats de sons.

Il n'y a pas de limite à la taille du buffer de son, celui-ci étant automatiquement découpé en petits tronçons par la librairie. les données sont copiées par la librairie: le paramètre USound et ses données associées peuvent être sans risque libérées dés le retour de la fonction .

Chapter 3. Réception

La plupart des messages reçus du serveur URBI sont le résultat d'une commande précédement envoyée. Le mécanisme des tags URBI autorise de lier un message à sa réponse: à chaque commande est associée un tag, et ce tag est répété dans le message de réponse. Les class UClient gèrent la réception de ces messages dans le thread indépendant créé par le créateur, les analyses et remplis une structure UMessage. Les retours de fonctions avec un tag associé peuvent être enregistrés avec la méthode registerCallback: chaque fois qu'un message avec ce tag est envoyé par le serveur, la fonction callback sera appelée avec une structure UMessage comme paramètre. Les deux formes de base de registerCallback sont:

typedef UCallbackAction (*UCallback)             (const UMessage &msg);
typedef UCallbackAction (*UCustomCallback)       (void * callbackData, const UMessage &msg);

UCallbackID 	setCallback (UCallback cb, const char *tag)
UCallbackID 	setCallback (UCustomCallback cb, void *callbackData, const char *tag)

le premier paramètre est toujours un pointeur vers la fonction à appeler. callbackData est un pointeur qui sera retourné à la fonction callback chaque fois quelle est appelée. La fonction callback doit retourner URBI_CONTINUE, ou URBI_REMOVE, dans ce cas la fonction ne sera pas enregistrée.

Quelques exemples:


UCallbackAction onImage(const UMessage &msg) {
//Test si quelqu'un a utilisé votre tag ou si un message d'erreur a été reçu
  if (msg.type != MESSAGE_DATA || ((UImage)msg).imageFormat == IMAGE_UNKNOWN)
    return URBI_CONTINUE;
  UImage img = (UImage)msg; 
  msg.client.printf("Image of size (%d,%d) received from server at %d\n",img.width, img.height, msg.timestamp);

  unsigned char *image = new unsigned char[img.width*img.height*3];
  int sz = img.width*img.height*3;

  if (img.imageFormat == IMAGE_JPEG)
    convertJPEGtoRGB((const byte *) img.data, img.size, (byte *) image, sz); //provided by liburbi
  if (img.imageFormat == IMAGE_YCbCr)
    convertYCrCbtoRGB((const byte *) img.data, img.size, (byte *) image);  //provided by liburbi

  myDisplayRGBImage(image, img.width, img.height);
  delete image;
  return URBI_CONTINUE;
}

UCallbackAction onSound(const UMessage &msg) {
 if (msg.type != MESSAGE_DATA || USound(msg).soundFormat == SOUND_UNKNOWN)
    return URBI_CONTINUE;

  //convert the sound to a wav 16KHz 16bit.
  USound snd;
  snd.soundFormat = SOUND_WAV;
  snd.rate = 16000;
  snd.sampleSize = 16;
  snd.sampleFormat = SAMPLE_SIGNED;
  snd.channels = 0; //take the value from source
  snd.data = 0;
  snd.size = 0;
  convert((USound)msg, snd); //Cette fonction est fournie par liburbi
  myPlayWAV(snd.data, snd.size);
  return URBI_CONTINUE;
}

UCallbackAction onJoint(const UMessage &msg) {
  if (msg.type != MESSAGE_DATA || ((UValue)msg).type != DATA_DOUBLE)
    return URBI_CONTINUE;
  msg.client.printf("The joint value si %lf\n", UValue(msg).val);
  return URBI_CONTINUE;
}

int main(int argc, const char * argv[]) {
  UClient * cl = new UClient(argv[1]);
  if (cl->error()) urbi::exit(1); //portability call explaned below
  cl->setCallback(&onImage, "img");
  cl->setCallback(&onSound, "snd");
  cl->setCallback(&onJoint, "joint");
  cl->send("img: camera.val;");
  cl->send("loop snd: micro.val,");
  cl->send("joint: headPan.val;");
  urbi::execute();  //portability call explaned below
}

UMessage

The UMessage structure is capable of storing the informations contained in any kind of URBI message by using a "type" field and an UValue (union of type-dependant structures). These two structures are defined as follows:

Les versions template de registerCallback sont définies aussi. Elles permettent de régler callbacks sur les fonctions des membres, avec des paramètres personnels de 0 à 4 de tout types (incluant pointeurs et références). La seule contrainte sur la signature de fonction est quelle doit renvoyer un UCallbackAction, et prendre une const UMessage& comme dernier paramètre. Quelques exemples:

class Test {
   public:
     UCallbackAction onJoint(int value, const UMessage &msg);
}:

UCallbackAction  Test::onJoint(int value, const UMessage &msg) {
  msg.client.printf("got a message at %d with tag %s, our int is %d\n",msg.timestamp, msg.tag, value);
  return URBI_REMOVE;  //unregister ourself
}

int main(int argc, const char * argv[]) {
  Test *a = new Test();
  UClient * cl= new UClient(argv[1]);
  if (cl->error()) urbi::exit(1);
  cl->setCallback(*a, &Test::onJoint, 12, "tag");
  cl->send("tag: headPan.val;");
  urbi::execute();
}

Chapter 4. Operations synchrones

La classe dérivée USyncClient implemente des méthodes pour récupérer de façon synchrone le résultat des commandes URBI. Vous devez être prévenu que ces fonctions sont moins efficaces, et quelles sont moins facilement portable.

Lire la valeur d'un périphérique

Pour lire la valeur d'un périphérique, vous pouvez utiliser la méthode syncGetDevice ou syncGetNormalizedDevice. Le premier paramètre est le nom du périphérique (par exemple, "neck"), le second est un double qui doit être remplit avec la valeur reçue. La différence entre les deux méthodes est que syncGetDevice retrouve la valeur avec une commande "val", en considérant que syncGetNormalizedDevice utilise "valn" (voir urbidoc.html pour plus de détails à propos de "val" et "valn").

double neckVal;
syncClient->syncGetDevice("neck",neckVal):

Recevoir une image

Vous pouvez utiliser la méthode syncGetImage pour récupérer une image de façon synchrone. La méthode enverra la commande appropriée, et attendra le résultat, donc elle bloquera votre thread en attendant que l'image soit reçue.

client->send("camera.resolution = 0;camera.gain = 2;");
int width, height;
client->syncGetImage("camera", myBuffer, myBufferSize, IMAGE_JPEG, URBI_TRANSMIT_JPEG, width, height);

Le premier paramètre est le nom de la camera. Le second est le buffer (void*) qui sera rempli avec les données de l'image. Le troisième devra être une variable nombre entier égale à la taille du buffer. La fonction règlera cette variable à la taille des données. Si le buffer est trop petit, les données seront tronçonnées.

Le quatrième paramètre est le format dans lequel vous voulez recevoir les données de l'image. Les valeurs possibles sont IMAGE_RGB pour une image raw RGB 24 bit par pixel, IMAGE_PPM pour une PPM file, IMAGE_YCbCr pour raw YCbCr data, et IMAGE_JPEG pour un fichier jpeg-compressed.

Le cinquième paramètre peut être l'un ou l'autre URBI_TRANSMIT_JPEG ou URBI_TRANSMIT_YCbCR et spécifier comment l'image doit être transmise entre le robot et le client. Transmettre des images JPEG augmente le frame rate et devra être utilisé pour de meilleurs performances.

Finalement les paramètres de largeur et de hauteur sont remplit avec la largeur et la hauteur de l'image en retour.

Recevoir un son

La méthode syncGetSound peut être utilisée pour recevoir des données sonores de toute longueur venant du serveur.

client->syncGetSound("micro", duration, sound);

Le premier paramètre est le nom du périphérique duquel on obtient le son, la seconde est la durée demandée, en millisecondes. Sound est une structure USound qui sera remplit avec le son enregistré sur la sortie.

Chapter 5. Fonctions de conversion

Nous avons aussi inclus quelques fonctions de conversion pour différents formats d'images et de sons. L'utilisation des fonctions de conversion d'image est trés claire:

int convertRGBtoYCrCb(const byte* source, int sourcelen, byte* dest);
int convertYCrCbtoRGB(const byte* source, int sourcelen, byte* dest);
int convertJPEGtoYCrCb(const byte* source, int sourcelen, byte* dest, int &size);
int convertJPEGtoRGB(const byte* source, int sourcelen, byte* dest, int &size);

Le paramètre size devra être réglé à la taille du buffer d'arrivée. En retour, il sera réglé à la taille du son en sortie.

Pour convertir des sons de différents formats, la fonction convert peut être utilisée. Elle prend deux structures USound comme paramètres. Les deux formats audio supportés sont SOUND_RAW et SOUND_WAV, mais la compatibilité avec des formats de sons compressés comme Ogg Vorbis et MP3 est prévue. Si chacun des champs est mis à zero dans la structure d'arrivée, les valeurs correspondantes du son de départ seront utilisées.

Chapter 6. Tout mettre ensemble: quelques exemples

Jetez un oeil aux exemples, Dans les répertoires "example" ou "utils" donnés avec liburbi. vous pouvez trouver:

  • urbiimage: Affiche les images prises par la caméra en temps réel, ou sauve un instantané dans un fichier.

  • urbisound: Joue le son venant du microphone du robot sur le haut-parleur de l'ordinateur, ou l'enregistre dans un fichier.

  • urbisendsound: Joue un fichier wav venant de l'ordinateur, sur le robot, en le convertissant si nécessaire.

  • urbiping: Envoie la commande URBI 'ping' à intervalles réguliers pour mesurer la latence.

  • urbibandwidth: Mesure la largeur de bande (bandwidth.) effective.

  • urbisend: Envoie un ensemble de commandes contenues dans un fichier au robot.

  • urbirecord: Enregistre tous les mouvements d'un robot dans un fichier.

  • urbiplay: Joue un fichier enregistré avec urbirecord, ou le met sous une forme lisible.

  • urbimirror: Copie les mouvements d'un robot dans un autre robot. Comme en reliant urbirecord à urbiplay, mais avec moins de latence.

  • urbiscale: Change la vitesse d'un fichier enregistré avec urbirecord.

  • urbireverse: Inverse un fichier enregistré avec urbirecord.

  • urbicycle: Detecte et extrait les cycles dans un fichier enregistré avec urbirecord.

  • urbiballtrackinghead: Portage de l'exemple OPEN-R ballTrackingHead vers URBI.

Chaque programme s'il est lancé sans option affichera sa ligne de commande et des informations additionnelles si besoin.

Chapter 7. Conseils de programmation

  • Sauf si ce que vous faites est insignifiant, essayez de ne pas utiliser les fonctions sync*. Elles sont moins efficaces que les asynchrones.

  • les retours de fonctions devront se faire le plus rapidement possible, les retours dépendant tous du même thread. Si vous avez des opérations gourmandes en temps, vous devriez créer un nouveau thread et utiliser des mécanismes de synchronisation comme des sémaphores ou des mutexes.

Chapter 8. Les fonctions de portabilité

Quand d'autres version de liburbi seront disponibles, (en particulier une version avec OPEN-R qui l'autorisera à tourner sur le robot), il sera posible de compiler le même code pour les deux librairies, si quelques règles sont respectées:

  • N'utilisez pas USyncClient.

  • Utilisez la méthode de UClient printf au lieu de la version standard.

  • Utilisez la méthode de UClient getCurrentTime au lieu des fonctions de stdlib.

  • Utilisez la fonction urbi::exit (dans le "urbi" namespace) au lieu d'exit.

  • A la fin de votre main, appellez urbi::execute.

  • N'utilisez pas de thread, ou toute autre fonction qui n'est pas implémentée dans la version OPEN-R de stdlib.

Appendix A. Copyright


THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE
TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR
"LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE
LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE
OR COPYRIGHT LAW IS PROHIBITED.

BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND
AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR GRANTS
YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF
SUCH TERMS AND CONDITIONS.

1. Definitions

   1. "Collective Work" means a work, such as a periodical issue,
   anthology or encyclopedia, in which the Work in its entirety in
   unmodified form, along with a number of other contributions,
   constituting separate and independent works in themselves, are
   assembled into a collective whole. A work that constitutes a
   Collective Work will not be considered a Derivative Work (as
   defined below) for the purposes of this License.  2. "Derivative
   Work" means a work based upon the Work or upon the Work and other
   pre-existing works, such as a translation, musical arrangement,
   dramatization, fictionalization, motion picture version, sound
   recording, art reproduction, abridgment, condensation, or any other
   form in which the Work may be recast, transformed, or adapted,
   except that a work that constitutes a Collective Work will not be
   considered a Derivative Work for the purpose of this License. For
   the avoidance of doubt, where the Work is a musical composition or
   sound recording, the synchronization of the Work in timed-relation
   with a moving image ("synching") will be considered a Derivative
   Work for the purpose of this License.  3. "Licensor" means the
   individual or entity that offers the Work under the terms of this
   License.  4. "Original Author" means the individual or entity who
   created the Work.  5. "Work" means the copyrightable work of
   authorship offered under the terms of this License.  6. "You" means
   an individual or entity exercising rights under this License who
   has not previously violated the terms of this License with respect
   to the Work, or who has received express permission from the
   Licensor to exercise rights under this License despite a previous
   violation.

2. Fair Use Rights. Nothing in this license is intended to reduce,
limit, or restrict any rights arising from fair use, first sale or
other limitations on the exclusive rights of the copyright owner under
copyright law or other applicable laws.

3. License Grant. Subject to the terms and conditions of this License,
Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
perpetual (for the duration of the applicable copyright) license to
exercise the rights in the Work as stated below:

   1. to reproduce the Work, to incorporate the Work into one or more
   Collective Works, and to reproduce the Work as incorporated in the
   Collective Works; 2. to distribute copies or phonorecords of,
   display publicly, perform publicly, and perform publicly by means
   of a digital audio transmission the Work including as incorporated
   in Collective Works;

The above rights may be exercised in all media and formats whether now
known or hereafter devised. The above rights include the right to make
such modifications as are technically necessary to exercise the rights
in other media and formats, but otherwise you have no rights to make
Derivative Works. All rights not expressly granted by Licensor are
hereby reserved, including but not limited to the rights set forth in
Sections 4(d) and 4(e).

4. Restrictions.The license granted in Section 3 above is expressly
made subject to and limited by the following restrictions:

   1. You may distribute, publicly display, publicly perform, or
   publicly digitally perform the Work only under the terms of this
   License, and You must include a copy of, or the Uniform Resource
   Identifier for, this License with every copy or phonorecord of the
   Work You distribute, publicly display, publicly perform, or
   publicly digitally perform. You may not offer or impose any terms
   on the Work that alter or restrict the terms of this License or the
   recipients' exercise of the rights granted hereunder. You may not
   sublicense the Work. You must keep intact all notices that refer to
   this License and to the disclaimer of warranties. You may not
   distribute, publicly display, publicly perform, or publicly
   digitally perform the Work with any technological measures that
   control access or use of the Work in a manner inconsistent with the
   terms of this License Agreement. The above applies to the Work as
   incorporated in a Collective Work, but this does not require the
   Collective Work apart from the Work itself to be made subject to
   the terms of this License. If You create a Collective Work, upon
   notice from any Licensor You must, to the extent practicable,
   remove from the Collective Work any reference to such Licensor or
   the Original Author, as requested.  2. You may not exercise any of
   the rights granted to You in Section 3 above in any manner that is
   primarily intended for or directed toward commercial advantage or
   private monetary compensation. The exchange of the Work for other
   copyrighted works by means of digital file-sharing or otherwise
   shall not be considered to be intended for or directed toward
   commercial advantage or private monetary compensation, provided
   there is no payment of any monetary compensation in connection with
   the exchange of copyrighted works.  3. If you distribute, publicly
   display, publicly perform, or publicly digitally perform the Work,
   You must keep intact all copyright notices for the Work and give
   the Original Author credit reasonable to the medium or means You
   are utilizing by conveying the name (or pseudonym if applicable) of
   the Original Author if supplied; the title of the Work if supplied;
   and to the extent reasonably practicable, the Uniform Resource
   Identifier, if any, that Licensor specifies to be associated with
   the Work, unless such URI does not refer to the copyright notice or
   licensing information for the Work. Such credit may be implemented
   in any reasonable manner; provided, however, that in the case of a
   Collective Work, at a minimum such credit will appear where any
   other comparable authorship credit appears and in a manner at least
   as prominent as such other comparable authorship credit.  4.

      For the avoidance of doubt, where the Work is a musical
         composition: 1. Performance Royalties Under Blanket
         Licenses. Licensor reserves the exclusive right to collect,
         whether individually or via a performance rights society
         (e.g. ASCAP, BMI, SESAC), royalties for the public
         performance or public digital performance (e.g. webcast) of
         the Work if that performance is primarily intended for or
         directed toward commercial advantage or private monetary
         compensation.  2. Mechanical Rights and Statutory
         Royalties. Licensor reserves the exclusive right to collect,
         whether individually or via a music rights agency or
         designated agent (e.g. Harry Fox Agency), royalties for any
         phonorecord You create from the Work ("cover version") and
         distribute, subject to the compulsory license created by 17
         USC Section 115 of the US Copyright Act (or the equivalent in
         other jurisdictions), if Your distribution of such cover
         version is primarily intended for or directed toward
         commercial advantage or private monetary compensation.
         5. Webcasting Rights and Statutory Royalties. For the
         avoidance of doubt, where the Work is a sound recording,
         Licensor reserves the exclusive right to collect, whether
         individually or via a performance-rights society
         (e.g. SoundExchange), royalties for the public digital
         performance (e.g. webcast) of the Work, subject to the
         compulsory license created by 17 USC Section 114 of the US
         Copyright Act (or the equivalent in other jurisdictions), if
         Your public digital performance is primarily intended for or
         directed toward commercial advantage or private monetary
         compensation.

5. Representations, Warranties and Disclaimer

UNLESS OTHERWISE MUTUALLY AGREED BY THE PARTIES IN WRITING, LICENSOR
OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR
OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE,
MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR
THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF
ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO
NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY
NOT APPLY TO YOU.

6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY
APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY
LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR
EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK,
EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

7. Termination

   1. This License and the rights granted hereunder will terminate
   automatically upon any breach by You of the terms of this
   License. Individuals or entities who have received Collective Works
   from You under this License, however, will not have their licenses
   terminated provided such individuals or entities remain in full
   compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will
   survive any termination of this License.  2. Subject to the above
   terms and conditions, the license granted here is perpetual (for
   the duration of the applicable copyright in the
   Work). Notwithstanding the above, Licensor reserves the right to
   release the Work under different license terms or to stop
   distributing the Work at any time; provided, however that any such
   election will not serve to withdraw this License (or any other
   license that has been, or is required to be, granted under the
   terms of this License), and this License will continue in full
   force and effect unless terminated as stated above.

8. Miscellaneous

   1. Each time You distribute or publicly digitally perform the Work
   or a Collective Work, the Licensor offers to the recipient a
   license to the Work on the same terms and conditions as the license
   granted to You under this License.  2. If any provision of this
   License is invalid or unenforceable under applicable law, it shall
   not affect the validity or enforceability of the remainder of the
   terms of this License, and without further action by the parties to
   this agreement, such provision shall be reformed to the minimum
   extent necessary to make such provision valid and enforceable.
   3. No term or provision of this License shall be deemed waived and
   no breach consented to unless such waiver or consent shall be in
   writing and signed by the party to be charged with such waiver or
   consent.  4. This License constitutes the entire agreement between
   the parties with respect to the Work licensed here. There are no
   understandings, agreements or representations with respect to the
   Work not specified here. Licensor shall not be bound by any
   additional provisions that may appear in any communication from
   You. This License may not be modified without the mutual written
   agreement of the Licensor and You.