OCaml write-only


h1 2/03/2008 10:23:00 PM

J'ai découvert un petit site sympa suite à une annonce sur la Caml-list: Anarchy Golf. Au golf, on fini un parcours en un nombre minimum de coups. Ici, on résout un problème en un nombre minimum d'octets de code. De nombreux langages sont disponibles, dont OCaml.

Le problème annoncé sur la Caml-list laisse rêveur: trouver un Quine qui soit aussi un palindrome. En clair: un programme dont l'exécution affiche le code du programme, et dont le code est un palindrome, i.e. identique qu'on le lise de droite à gauche ou le contraire. Il est assez simple d'écrire un Quine, quel que soit le langage. J'ai écrit un palindrome en Caml, c'est déja un peu plus ardu. Mais satisfaire aux deux contraintes en même temps est décourageant. Alors j'ai perdu mon temps sur d'autres problèmes plus simples, en attendant de voir les solutions à celui-ci (le 08 Février, 01:46:39 heure japonaise).

La contrainte de concision force à faire des choses illisibles. Par exemple, ma solution pour le problème rot13:
let rec(!)?(c=input_byte stdin)x=output_byte stdout[|c;c;65+c mod x;97+(c-6)mod x|].(c/32);!x;;!26

À ce stade vient l'expression consacrée à Perl: ce code est write-only. Et encore c'est assez lisible comparé aux solutions du champion de Golf en OCaml: ksk.

La concision force à faire des choses qu'on ne pourrait qualifier de bonnes pratiques de programmation. Cela dit, il faut en général réfléchir à un algorithme pas trop con pour pouvoir prétendre au podium. Par exemple, même en déployant toutes les ruses de compression possibles, je sentais bien que je n'atteindrais pas les 54 octets de ksk pour écrire un interpéteur Scheme minimal. Après la fin du défi, quand son code a été publié, j'ai pu constater qu'il avait rusé:
print_string[|"3";"24";"5
8
58"|].(-Obj.magic(@)mod 3)

De là à comprendre la ruse, ce n'est pas si simple. Tout repose sur le fait que le code ne doit pas vraiment implémenter un interpréteur Scheme: il suffit qu'il donne les bonnes réponses pour trois entrées différentes, toujours les mêmes. La plupart des participants ont utilisé cette "faille" pour répondre, par exemple m.ukai, classé second en OCaml:
print_string[|"24";"3";"5
8
58"|].(String.length(read_line())-16)

Ici, la réponse est choisie en fonction de la longueur de la première ligne de l'entrée, cette information étant suffisante pour distinguer les tests. La solution de ksk reste incompréhensible: il n'y a aucune instruction de lecture, aucun test dépendant de l'entrée. Pire: si vous prenez son code et le compilez (en natif ou bytecode) vous obtiendrez un programme déterministe (il affiche toujours 3 chez moi).

La clé du mystère ? Exécuté dans la boucle d'interaction ocaml comme le fait la procédure de vérification du Code Golf, ce code a une sortie aléatoire. Selon les exécutions de ocaml ksk.ml, on reçoit la réponse de l'un des trois tests. Le hasard réside dans l'initialisation de la boucle d'interaction, qui fait que l'opérateur de concaténation (@), transtypé en un int par Obj.magic va avoir une valeur grosso-modo aléatoire. Je ne fais que constater, je ne comprends pas.

Mais cela ne répond toujours pas au problème... enfin, pas toujours. Il aura probablement fallu à ksk pas mal de patience pour soumettre et re-soumettre son code jusqu'à ce que le hasard renvoie les bonnes réponses aux bons tests, et le classe premier! Cette solution est vraiment write-only: c'est illisible, et même son exécution laisse à désirer :)

Moi je trouve ça un peu trop immoral, mais il faut avouer que ça respecte les règles du jeu. Et on aura appris quelquechose. C'est moche, mais ça marche.

Libellés : , ,

Application


h1 7/18/2007 12:21:00 PM

Je suis en conférence, ce qui me laisse plein de temps pour bosser pendant la plupart des exposés... Bremen est plutôt une jolie ville, le campus est super agréable et il fait beau. Blogger s'est mis à me parler en Allemand. Ici j'ai vu Al. Bundy, pour ceux qui connaissent...

Bon sinon, un petit exemple simple m'est venu pour montrer que l'application en Caml n'est pas aussi simple qu'on le pense en présence d'arguments optionels. L'équation f x y = (f x) y, n'est pas toujours vérifiée. En fait, on n'a pas seulement une notion d'application mais bien une multi-application. Si je parle de ça c'est pour mettre au clair l'origine de la multi-abstraction du langage de script de liquidsoap.

# let f ?(a=false) () = a ;;
val f : ?a:bool -> unit -> bool =
# f () ~a:true ;;
- : bool = true
# (f ()) ~a:true ;;
This expression is not a function, it cannot be applied
# f () ;;
- : bool = false

Libellés : , , , , ,

Un type bien


h1 6/27/2007 02:39:00 AM

La fête du ciné se termine. Je n'ai vu que deux films, Boulevard de la Mort et Dialogues avec mon Jardinier. J'ai beaucoup aimé les deux. Ils sont très différents, mais ont cependant un point commun: on sort des deux en réalisant comme les choses simples sont efficaces. Pour le premier je pense à la simplicité de la violence et à celle du ressort "vengeance" qui fait jubiler le public comme rarement. Pour le second il s'agit de la simplicité du retour aux sources, à la nature, au jardinage, s'opposant à la vie urbaine et une création artistique sur-intellectuallisante.

Je ne peux pour ma part me résoudre à la simplicité, et, cherchant de nouvelles lectures, je suis retombé de plus belle sur Philip K. Dick, l'embrouilleur de réalités, avec le volume un (sur deux) de l'intégrale de ses nouvelles. Et, pour changer, je code: j'enrichis le système de type de liquidsoap, et je saisis cette occasion pour écrire un quelques mots sur l'inférence de type.

Pourquoi typer ?

Lors de l'exécution d'un programme, si on ne fait rien pour s'en prémunir, certaines choses absurdes peuvent arriver. Une fonction à binaire peut ne recevoir qu'un argument, on peut se retrouver à additioner un entier et un couple de chaînes de caractères, etc. Il est difficile de prévoir exactement ces problèmes sans exécuter le programme, et cela devient impossible dès lors que le langage devient Turing puissant.

Une première approche consiste à ne rien faire, et laisser le programmeur attendre que l'erreur à l'exécution arrive. C'est litéralement nul, il faut donc donner un nom à cela ("dynamique") et balancer quelques slogans qui font bien ("de toutes façons les tests unitaires sont plus précis que le typage") pour faire bonne figure. La majorité des langages de scripts adoptent cette approche. Selon l'expression absurde à évaluer, une vraie erreur va être levée, ou l'interpréteur va adopter une solution et continuer. Par exemple, si une fonction n'est pas appelée avec tous ses arguments, l'un d'eux pourra avoir la valeur spéciale undefined. Tôt ou tard, cette valeur spéciale posera une vraie erreur (on peut imaginer que l'interpréteur refuse catégoriquement de l'additionner avec quoi que ce soit), et il sera assez difficile pour le programmeur de trouver d'où venait cet undefined, qui aura pu passer de fonction en fonction avant de poser réellement problème. Je ne vais pas en dire plus sur cette approche qui ne fait rien pour le programmeur.

Le typage peut être vu comme une sur-approximation de l'ensemble des comportements qui peuvent arriver. Parfois le typage va donc détecter un problème qui ne peut pas vraiment arriver -- si on écrit une expression absurde dans une branche morte du code. En pratique, ça reste naturel. Quelques exemples rapides: 3 aura le type int; ("trois",3) le type string*int; et la fonction fun x -> x+1 le type int ->int.

Le typage statique consiste à vérifier les types avant l'exécution du programme. Dans certains langages, le programmeur écrit les types de ses variables (C/C++, Java, ...). Cela peut devenir assez lourd. D'autres langages (OCaml, SML...) sont capables de calculer le type le plus général d'une expression, c'est ce qu'on appelle l'inférence. Quand un langage a cette propriété, c'est la panacée: on n'écrit pas les lourdes annotations de type, et on a une vérification statique du code. De plus, aucune information de type n'étant nécessaire à l'exécution, les valeurs sont représentées de façon plus compacte et manipulées de façon plus immédiate, et on obtient des programmes plus rapides.

Le bémol du typage statique inféré, c'est la complexité des messages en cas d'erreur de type. D'autre part, si le système est trop simple, cela contraindra trop le programmeur dans son utilisation du langage. Mais quand on étend le système de type, on complexifie rapidement les erreurs. Il y a là un compromis délicat.

Inférer les types

Comment deviner le type d'une expression? Il suffit de regarder de près, en commencant par les expressions élémentaires et en remontant... Prenons l'exemple de fun x -> x+1.

On commence sur 1, qui a évidemment le type int. Ensuite on rencontre x, dont le type est inconnu. Mais pour que x+1 soit valide il faut bien que x soit un entier. La fonction a donc le type int -> int. Si je la nomme f, on peut maintenant chercher le type de fun y -> f("bla"). On voit qu'on utilise f avec une chaîne comme paramètre au lieu d'un entier, c'est une erreur de type.

Polymorphisme

Une extension simple et qui fonctionne bien est le polymorphisme -- ou l'appelle généricité dans certains langages. Si j'écris fun x -> x, je ne fais aucune hypothèse sur le type de mon argument, et je pourrais utiliser cette fonction sur n'importequelle valeur. C'est possible, OCaml lui donnera par exemple le type 'a -> 'a, qui veut dire pour tout a, a -> a. Exemple moins bidon: la fonction qui retourne une liste ne fait aucune hypothèse sur le type des éléments de la liste, et aura le type 'a list -> 'a list. (Exercice pénible: faites la même chose avec Java...)

Comme inférer ces types ? Il suffit de généraliser sur les variables de type (1) qui ne sont pas instantiées et (2) qui ont été introduites dans la fonction. Quand on type fun x -> x, on vérifie le corps de la fonction sans avoir besoin d'imposer aucune contrainte au type de x, c'est encore une inconnue, que Caml note '_t. L'expression a donc le type '_t -> '_t, qu'on généralisera en 'a -> 'a.

Si par contre je type fun x -> fun y -> x, il faut faire un peu plus attention. Mauvaise solution: la fonction interne a le type '_Ty -> '_Tx, que je généralise en 'a -> 'b, l'expression complète a donc le type '_Tx -> ('a -> 'b), généralisé en 'c -> 'a -> 'b. C'est manifestement faux, car cela veut dire: je prends deux paramètres quelconque et je te renvoie un truc de n'importe quel type.

Si on applique la condition (2) sur l'exemple fun x -> fun y -> x, voila comment cela se passe. La fonction interne a le type '_Ty -> '_Tx, mais '_Tx n'a pas été introduit dans cette fonction interne. On ne généralise qu'en 'a -> '_Tx. La fonction externe a donc le type '_Tx -> 'a -> '_Tx, qu'on généralise en 'b -> 'a -> 'b. C'est correct, cela veut dire: donne moi deux paramètres, je te renvoie un truc du type du premier.

Subtilités avec les références

Encore un petit détail avant de conclure. La liste vide [] peut sans problème avoir un type polymorphe 'a list. Il n'en est pas de même pour une référence ref [], qui doit garder le type '_T list ref, sans généralisation sur '_T. On ne modifie jamais une liste: on en crée une autre qui pointe sur l'ancienne. Et il n'y a aucune problème à ce qu'une liste d'entiers finisse en pointant la même liste vide qu'une liste de chaines. Par contre on modifie une référence, c'est sa raison d'être. Si on utilise notre référence dans l'instruction r := [1] on y range une liste d'entiers cela doit forcer '_T à devenir int. Car si par la suite on utilise un élément de la liste contenue dans la référence, cela ne peut pas être n'importe quoi, ce sera un entier.

Exercice/exemple: vérifier que fun x -> ref [x] a le type 'a -> 'a list ref; que let r = ref [] in (r, fun x -> r := [x]) a le type '_T list ref * ('_T -> unit).

Liquidsoap

Pourquoi j'ai parlé de tout ça ? Ces jours ci, je rafraichis le code de l'inférence de type de liquidsoap. J'ai ces choses en tête et je crois que cela vaut le coup de les présenter. Bien sûr je n'ai pas détaillé tout ce qu'il y avait dans le système de type de liquidsoap. Mais tout ce que j'ai décrit s'y trouve. Ce qui me mène à mon second but: expliquer pourquoi toute cette technologie compliquée est nécessaire dans liquidsoap.

Pour être honnête, ce n'est nécessaire que si l'on accepte le choix des types statiques et inférés dans le langage de script de liquidsoap. Pour moi il n'y a pas photo. Ensuite, on a besoin de fonctions dans le langage pour représenter les transitions. Enfin, nous prévoyons de généraliser liquidsoap, de générateur de flux audio à générateur de flux quelconque, en ajoutant la vidéo dans un premier temps. Pour cela, il faut du polymorphisme: au lieu des objets de type source on aura des objets de type format source où le format pourra être audio, video, etc. avec des informations en fait un peu plus raffinées. Certaines de fonctions actuelles de manipulation de flux, comme le switch() qui passe d'un flux à l'autre, s'étendent naturellement à n'importe quel format. Elles auront donc un type polymorphe. Enfin, si j'ai parlé des limitations de la généralisation pour les références, c'est parce que les types source se comportent pareil.

Le truc chouette, c'est que ça me donne une super excuse pour coder ces trucs que j'adore -- et que je vais enseigner l'an prochain en TP d'un cours de M1 de l'X, au passage. Et même avant l'arrivée du support vidéo, ça rend le langage plus souple et sympa pour un nombre considérable de choses. Mais je ne vais pas me lancer là dedans, c'est assez...

Libellés : , , ,

Création


h1 6/15/2007 10:01:00 AM

Je ne code plus trop ces temps-ci, après la release de liquidsoap-0.3.3. Déja, je suis pas mal occupé au labo, et ça porte ses fruits. Mais de toute façon, il faut bien qu'il y ait des périodes moins frénétiques: c'est un projet sans deadline ni client. Aucune raison de se faire du mal pour apporter une amélioration montre en main. Me voilà donc dans une période tranquille, où je me demande plutôt ce que je pourrais faire de créatif à la place.

Mes pensées vont par exemple à ce projet de projet de jeu de piste (ou ARG?) radiophonique, parti de la numbers station de Balbinus... Disséminer des pistes, principalement par le biais de quelques webradios amies, vers les éléments d'une histoire/énigme/univers caché et mystérieux. Mais quelle histoire raconter, quelle idée faire passer? Parler de l'excès de communication, de l'usure des mots? des excès de l'individualisme?

En parlant d'alliance création/technique, je suis tombé sur la ménagerie, un studio d'animation amateur sympathique, qui lie developpement open-source et création d'animations en stopmotion (jetez un oeil à la section studio, par exemple cette surprise..) Chapeau bas!

De façon moins intellectuelle, je me demande si j'accrocherais à des jeux à histoire comme un Final Fantasy ou Castlevania sur DS; et si le EEE PC sortira vraiment en Aout, au prix annoncé et en quantité suffisamment énorme :)

Libellés : , , , , ,

Sucre


h1 5/18/2007 02:21:00 AM

Déja un mois depuis mon dernier post, il est temps d'en aligner un autre, dans la lignée des notes bien techniques. Le developpement de liquidsoap continue sûrement et plutôt pas lentement. L'API s'étoffe, notamment avec le crossfade intelligent utilisant les intensités relatives des débuts/fins de fichier, et des traitements de son de fou grâce à la libsoundtouch; le format AAC est supporté; nous n'avons presque plus à rougir du support ALSA; la doc devient plus lisible; et une multitude de petits détails s'améliorent. Romain nous pousse à releaser au plus vite, ce qui devrait se faire vu les grands chantiers qui se profilent de nouveau à l'horizon. Pour ma part j'oscille entre l'excitation de voir tout cela si bien tourner, et l'énervement de passer beaucoup de temps à orchestrer ça, corrigeant ou ajoutant du code selon des priorités qui ne sont plus seulement les miennes: tel utilisateur insiste sur un point, tel développeur fait une modification pas forcément judicieuse... et je ne peux m'empêcher de vite intervenir, peut être à tort.

Après la release, ma priorité sera à l'enrichissement du système de type de liquidsoap, avec du polymorphisme et du sous-typage, amenant une simplification de certains opérateurs, et surtout la possibilité de faire cohabiter des sources manipulant différent formats de flux. Après ça, Sam va pouvoir se faire plaisir avec la vidéo... On envisage aussi de généraliser les notions de fade et de crossfade intelligent au delà de la simple mesure de volume: on peut désormais mesurer et changer le BPM d'une source, on se prend à penser à des transitions préservant la continuité du BPM. Voila pour le futur. J'espère parler de typage la prochaine fois, mais pour tout de suite je vous propose encore de l'analyse syntaxique.

Certains sucres syntaxiques semblent tout simples, mais ne rentrent pas dans la sacro-sainte architecture lex/yacc. Par exemple, le preprocessing, qui permet de n'inclure certaines parties du script que quand telle feature est activée: %ifdef FEATURE CODE %endif. Cela ne peut être traité par le parser, ne serait-ce que parce que la construction peut être utilisée à tous les niveaux de la grammaire, ce qui l'alourdirait considérablement. Autre exemple, la notation bien pratique à la Ruby: "Bonjour #{get_name()}!" au lieu de "Bonjour "+get_name(). Ce n'est pas faisable dans le lexer tout seul, et pénible à traiter au niveau du parser, qui en théorie ne dépend pas du lexer.

La solution est d'accepter de s'éloigner un chouilla de l'archi habituelle, en ajoutant des intermédiaires entre analyse syntaxique et lexicale. C'est particulièrement simple en Caml, où un lexer n'est autre qu'une fonction prenant un état (position dans le texte) et renvoyant le symbole suivant dans le flux. On peut donc écrire des fonctions pour enrober le lexer original, afin d'obtenir les notations décrites -- et on simplifie par ailleurs notre lexer en traitant de la même façon les retours à la ligne pour lever les ambiguités dont je parlais la dernières fois. Un petit exemple, le traitement des %ifdef:

(* takes a function of type Lexing.lexbuf->Lang_parser.token,
and returns a function of the same type *)
let preprocess tokenizer =
let state = ref 0 in (* number of nested %ifdef *)
let rec token lexbuf =
match tokenizer lexbuf with
| Lang_parser.PP_IFDEF ->
begin match tokenizer lexbuf with
| Lang_parser.VAR v ->
if feature_enabled v then begin
incr state ;
token lexbuf
end else
let rec skip () =
match tokenizer lexbuf with
| Lang_parser.PP_ENDIF -> token lexbuf
| _ -> skip ()
in
skip ()
| _ -> failwith "expected a variable after %ifdef"
end
| Lang_parser.PP_ENDIF ->
if !state=0 then failwith "no %ifdef to end here" ;
decr state ;
token lexbuf
| x -> x
in
token


Après avoir défini quelques sucres syntaxiques, on utilise tout simplement le lexer étendu expand (preprocess (strip_newlines Lang_lexer.token)) au lieu de Lang_lexer.token. L'exemple complet se trouve sur notre SVN.

Libellés : , , ,

Grammaire


h1 4/20/2007 11:54:00 AM

J'ai parlé ici-même, il y a presque un an, d'une petite astuce pour obtenir un langage sans point-virgule dans les séquences, juste un peu whitespace-sensitive, tout en utilisant le générateur de parser (ocaml)yacc. Je suis toujours dans cette optique, car je veux garder cette syntaxe légère, et je crois toujours qu'écrire mon parser à la main est source d'emmerdes au moins à long terme.

Résume de l'épisode précédent: le seul endroit où le retour à la ligne compte, c'est pour distinguer f ; (1,2) de f(1,2). Bien sûr la première expression n'a peu d'intérêt, la séquence ne servant qu'aux effets de bord que f ne fait pas, mais cela ne se voit pas au stage de l'analyse syntaxique. Ma solution a été d'introduire un token VARLPAR qui regroupe la variable et la parenthèse qui la suit si elles ne sont pas séparées par un retour à la ligne.

Second épisode: liquidsoap est en plein boom. Nous avons de nouveaux contributeurs, de nouveaux utilisateurs, on parle de nous, de nouvelles idées sont implémentées, etc.

Une de ces idées est le cross-fading intelligent. Au lieu de passer au cross-fading une fonction de transition qui décrit comment sont combinés les débuts et fins de piste, on y passe une liste de (condition,transition). La condition est une fonction à qui le smart_cross passe les puissances du signal sonore de part et d'autre de la transition. L'opérateur choisit la première fonction de transition dont la condition est validée. On peut ainsi choisir de superposer, fader, en fonction du volume des débuts et fin de piste. Romain a commencé à coder dans utils.liq l'utilisation du smart cross selon la description que mouke de bide-et-musique nous a faite.

Cet opérateur amène le besoin de coder dans notre langage de script des expressions arithmétiques, des comparaisons, etc. Et j'ai donc du étendre la grammaire. Pour faire simple, disons qu'on ajoute *, /, + et -.

Premier conflit: comment distinguer f(x) \n -1 de f(x)-1. Les deux sont naturels, on ne peut en éliminer aucun d'office. A moins d'empêcher le programmeur d'éclater ses expressions arithmétiques sur plusieurs lignes, ce qui est peu naturel et de toute façon difficilement réalisable dans mon cadre.

La solution vient de zmdkrbou, qui me propose de distinguer les expressions closes cexpr et les expressions générales expr. Une expression close peut être utilisée sans ambiguité dans une séquence: pour cela on limite les symboles pouvant apparaitre en tête, i.e. on élimine donc les négatifs -42, qui ne sont présents que dans les expressions générales. On peut obtenir une expression close représentant un négatif en l'entourant de parenthèses. Dans la plupart des règles, on peut continuer à utiliser des expressions générales, mais la règle de séquençage d'expression est restreinte aux expressions closes. Ainsi f(x) - 1, où que soient les retours à la ligne, sera la valeur de f(x) décrémentée de 1. Et on écrira f(x) (-1) (en général sur deux lignes, mais rien n'oblige) pour signifier qu'on évalue f(x) et qu'on retourne -1.

On tient le bon bout, mais l'histoire ne s'arrête pas là, et yacc m'en a fait baver. On a dans notre grammaire la règle (1) expr: cexpr, qui dit qu'une expression close est aussi une expression, et la règle (2) cexpr: cexpr PLUS expr. J'avais laissé un expr à droite du PLUS car l'ambiguité vue ci-dessus n'est présente qu'en début d'expression.

Sachant que l'automate d'analyse syntaxique doit décider de ce qu'il fait en ne regardant qu'un symbole en avant, comment savoir dans la situation cexpr PLUS ... si la cexpr doit être considérée comme une expr, auquel cas on réduit la règle (1), ou s'il faut avancer dans la règle (2) en mangeant de suite le +. L'automate est bête, pour lui une addition peut arriver après une expression générale car une expression générale peut être, en particulier, close. On ne peut le guider qu'avec des priorités, je mets donc une priorité plus faible à la coercion (1) qu'à la règle (2).

Et là, c'est le drame. Les priorités entre l'addition et la multiplication ne sont plus du tout respectées, l'analyseur parenthèse tout à droite comme un sale. On lui donne 2*3+1 il lit 2*(3+1). Après masse de bataille, je m'aperçois que la formulation de la règle (2), bien qu'inambigue, pose problème. On a expr: cexpr PLUS expr | cexpr MULT expr .... Le problème est que l'automate défavorise maintenant la coercion expr:cexpr. Du coup quand il voit le + après le 3 il ne va pas réduire l'expression close 3 en expression pour réduire ensuite l'expression close 2*3. Il va préférer manger le +, ce qui conduit à réduire l'expression close 3+1 en premier.

Au final la règle (2) doit s'écrire cexpr: cexpr PLUS cexpr. Et on n'y perd pas trop. On ne peut plus écrire 3+-1 mais il faut parenthéser en 3+(-1).

La morale: c'est toujours possible d'utiliser le couple lex/yacc pour une syntaxe sans point-virgule, en limitant énormément la sensibilité aux blancs (espace et retour à la ligne), ouf. Par contre l'automate de yacc est bien bête sur une production du type expr: cexpr.

Un jour faudra que je voie si c'est si difficile de ne pas ignorer les retours à la ligne à l'analyse syntaxique, et de les traiter explicitement partout dans la grammaire. A priori c'est lourd, comme il ne compte pas dans la majorité des cas. Mais une astuce n'est pas à exclure..

Libellés : , ,

J'en peux plus


h1 3/22/2007 06:01:00 PM

En fin de semaine dernière j'ai donné un coup de collier pour releaser liquidsoap 0.3.2, merci au passage à Romain qui s'est occupé des releases des librairies associées, et du packaging Debian par derrière. Le but pour moi était de passer ensuite une semaine studieuse à écrire mon article. Impossible.

Lundi nous recevons sur la liste savonet-users un mail de mouke. Je cite car l'archive de la mailing-list est plus que pourrie:
I recently discovered LiquidSoap and it seems it is the streamer I have dreamed of for some months. I am the tech guru of the french webradio "Bide et Musique".

Mouke a déja un clone de Bide et Musique qui tourne avec liquidsoap et songe à distribuer son système de gestion de la programmation musicale. La description qu'il en fait est alléchante, cela ferait un beau compagnon à liquidsoap!

Nous avons de plus en plus de résidents sur le canal IRC #savonet chez freenode. La plupart utilisent liquidsoap pour leur radio underground ou étudiante. Avec l'arrivée de ce mail tous les français se sont excités. J'ai passé la nuit à coder, sur la sortie ALSA et plusieurs autres détails; Samuel s'y est remis aussi et a mis au point un opérateur de renormalisation dynamique du volume; Balbinus s'est laissé pousser à coder un opérateur de compression. Et Mouke interface liquidsoap avec son outil. Quand j'arrive à décrocher je me fais rappeler à l'ordre par masse de questions et remarques. Je vais demander aux admins du labo de me désinstaller xchat...

Mais ce n'est pas tout. Car O'Reilly parle de nous, comme me l'apprend Romain: Une radio scriptable avec Liquidsoap et Savonet. Je n'étais pas au courant, mais je remercie Xavier Cazin, qui avait découvert liquidsoap par hasard il y a un an.

Ce genre de phénomène est exponentiel. Ça va dépoter :)

Libellés : ,

Pendant ce temps là...


h1 3/16/2007 08:49:00 PM

Pas posté depuis longtemps. Tout va bien, c'est le Printemps. La lumière est belle le soir en retrant de l'école, et l'herbe sent comme une fille qui sort de la douche. Si la bonne recherche c'est d'ouvrir des questions plutôt que d'en fermer, ça ressemble à ce que je fais... J'aurais bien aimé parler de beau code, malheureusement je me suis empêtré dans mes constructions et le temps me manque. Il semble que je doive remettre ça à cet été, où on aura d'ailleurs encore un visiteur codeur pour jouer avec moi. Voici quand même un petit screencast amusant -- mais long à charger:

Libellés : , ,

En attendant...


h1 2/12/2007 07:37:00 PM

Je n'ai toujours pas reçu ma carte Arduino. Je m'en étonne ce matin et on me répond (comme par hasard) qu'elle partira aujourd'hui, problèmes avec un fournisseur, etc. On verra...

En attendant, le jeu Another World a fêté ses quinze ans. Pour l'occasion, l'éditeur nous "offre" une édition spéciale, avec des décors réhaussés (nombre de couleurs augmenté, détails ajoutés) et (enfin) les graphismes vectoriels rendus en haute résolution. Je dis "offre" parce que c'est payant, uniquement disponible pour Windows. Ya certainement plein de gens qu'aimeraient voir la recette de ce mythe. À la place l'éditeur préfère se faire quelques recettes supplémentaires, qui seront probablement ridicules, d'autant plus que la nouvelle édition est déja craquée. Pour ma part j'étais chaud pour payer, mais j'ai voulu tester d'abord si la démo tournait sous Wine. Le non-émulateur fait tourner Warcraft III et WoW, mais.. pas Another World. Ils ont en effet trouvé le moyen d'utiliser les dernières fonctionnalités de DirectX, pour un jeu qui tourne sans problème sur NES ou GBA...

Au final, j'ai ressorti ma ROM GBA, installé VisualBoyAdvance, et re-fini le jeu. C'est assez court quand on se souvient des ficelles -- et (j'avoue) qu'on zieute la soluce une ou deux fois pour ce dont on ne se souvient pas. Mais ce n'est pas pour ça qu'Another World doit être le seul jeu que j'ai fini plusieurs fois. C'est parceque c'est beau (même tout pixelisé) et c'est bien (même si l'histoire est linéaire) et c'est de la balle.

Allez, un petit site marrant au passage: philosophie.ppt.

Libellés : ,

Art & Technique


h1 1/28/2007 03:52:00 PM

On pourrait croire que je vais me plonger/noyer dans le débat "la programmation est-elle un art ?", et bien non, je ne vais même pas parler de programmation. Ne pas en parler fournit cependant une excuse valable pour y mettre mon grain de sel, j'en profite... Mon avis personnel est que la programmation est une maladie. Plus sérieusement, je pense que cela relève plus de l'artisanat que de l'art, avec tout l'amour du travail bien fait qui est présent dans beaucoup d'autres disciplines, mais une notion de bien fait qui n'existe pas avec l'art, et qui est liée à la fonctionalité du programme. Les poèmes et autres palindromes écrits dans des langages de programmation, n'ont (dans les cas que j'ai rencontrés) aucun intérêt en tant que programmes.

Revenons à nos moutons. Je voudrais parler de Gumstix, Arduino, PureData et Liquidsoap. L'histoire commence il y a bien longtemps avec la panne de notre Nabaztag. Le lapin wifi est une réussite niveau design, mais n'est pas super solide, et les utilisations possibles de l'engin étaient un peu restreintes: il ne communique qu'avec le serveur officiel, certains services sont payants, on ne peut que jouer de courts fichiers sonores. On peut dire que c'était juste assez bien pour que je commence à penser à le remplacer, mais pas assez pour que je sois prêt à débourser 130€, même pour la nouvelle version avec un nombril-microphone. Bref, je me suis mis à chercher un moyen de fabriquer mon propre lapin wifi, si possible en re-utilisant des pièces. Voici deux trois trucs sur lesquels je suis tombé.

Gumstix

Piloter une carte wifi est une chose compliquée, et en plus ça consomme beaucoup. En revanche il semble relativement aisé de bricoler des circuits communiquant via Bluetooth ou Zigbee. Très bien, mais comment faire le lien avec Internet? Il faut un serveur qui y soit branché et communique avec le lapin. Dans un premier temps ce sera mon portable, mais à terme je crois que ce sera un Gumstix bricolé: un micro-PC linux, plus compact tu meurs, et relativement bon marché.

Arduino

A plus court terme il faut un circuit pour contrôler le haut-parleur, les moteurs et lampes du lapin. Le LEGO Mindstorm c'est rigolo, ça pourrait sûrement faire tout ça, mais c'est pas très compact et surtout super cher. Les microcontrolleurs programmables ça fait peur... Jusqu'à ce que je tombe sur le projet Arduino. Il s'agit d'un projet ouvert (code source et spec. matériel distribuées sous licences libres) qui développe des cartes programmables, avec pour cible première des étudiants en art. Ils semblent avoir un truc facile d'accès, qui tourne avec des outils libres (gcc + IDE à eux), supporté par une certaine communauté. A première vue c'est donc une solution à la fois sérieuse (vrai microcontrolleur, qui parle à n'importe quel composant électronique, qu'on peut à terme monter dans un circuit indépendant de la carte) et assez simple d'accès pour laisser un peu de place à la créativité.

J'ai craqué et acheté le starter kit (carte + matos électronique de base) pour 50€, avec pour premier but de piloter un équivalent du nabaztag depuis mon portable. Ensuite je rajoute le streaming audio, la communication sans-fil, et.. qui sait ?

PureData et Liquidsoap

La communauté des bricoleurs sur Arduino semblent utiliser de temps en temps PureData, je me suis donc repenché un peu sur cet outil.
PD (aka Pure Data) is a real-time graphical programming environment for audio, video, and graphical processing. It is the third major branch of the family of patcher programming languages [...]

Bon. Realtime, ça veut rien dire, tout est temps réel avec assez de puissance. J'arrive pas à comprendre ce qu'est Patcher, si c'est vraiment un langage ou une interface graphique. Je sais pas en quoi PD permet de manipuler audio, vidéo et images de façon plus intéressante que le C. Faudrait que j'essaie ça.

Toujours est-il que PD a l'air très prisé pour réaliser des outils interactifs de synthèse et traitement audio. La question est: quelle différence avec Liquidsoap? Le but initial est différent, jusque là je ne me posais donc même pas la question. Mais après tout ce sont deux architectures assez génériques de traitement de son.
  1. La configuration de Liquidsoap n'est pas modifiable à la volée.
  2. Liquidsoap n'a pas d'interface graphique pour composer son script.
  3. Les possibilités d'interaction avec les sources sont limitées (interface telnet).
  4. Liquidsoap a peu de fonctions de synthèse de son, pas de feedback.
  5. On a pas d'entrée microphone stable.
Le premier point est délicat, c'est une modif non triviale que je ne ferais pas de sitôt, mais c'est faisable sans aucun doute. Les points 2-4 requierent encore pas mal de code mais aucune modif bien profonde, rien d'infaisable. Il faudrait bosser sur un protocole expressif et structuré pour interagir avec les sources -- à moins qu'on continue avec le telnet, la source mix() marche plutôt bien comme ça, pilotée depuis la GUI en python. Le point 5 requiert du code et du courage, j'ai aucune envie de me faire chier avec le hardware, mais c'est clairement faisable. Au final, j'ai envie de conclure qu'avec quelques contributeurs motivés pour pousser le projet dans cette direction, c'est faisable.

Mais quel avantage par rapport aux projets existants? Un premier truc qui distingue Liquidsoap d'un paquet de toolkits est que sa notion de flux comporte un notion de piste, ce qui permet d'avoir des opérateurs qui opèrent au niveau de la piste (sélecteurs, transitions entre pistes, etc.). Sinon, l'archi est propre et robuste grâce au typage de Caml. Enfin, ça me semble pas mal d'avoir un démon et un langage de script sous l'interface graphique, même si du coup on a pas d'interface graphique dans un premier temps.

J'en apprendrai peut-être plus aux Rencontres Mondiales du Logiciel Libre 2007 cet été à Amiens. J'ai décidé d'y présenter Liquidsoap! Je guette l'appel à présentations pour le thème multimédia sur leur fil RSS. Pour l'instant seuls les thèmes Communautés et Embarqué ont été appelés: ordre de préférence, organisateurs plus rapides?

Libellés : , ,

Browser shots


h1 1/21/2007 03:03:00 AM

Un petit post pour noter une adresse bien utile: BrowserShots. Ce site gratuit permet d'obtenir des captures d'écran de plusieurs navigateurs sur la page de votre choix. Bien utile pour s'apercevoir que sa page n'est pas complètement portable malgré la validité du XHTML...

Libellés : ,

Bye bye 2006...


h1 1/02/2007 10:44:00 AM

Bonne année 2007 à tous ! L'année 2006 aura été riche d'évenements, jusqu'au bout: EUCD.info nous apprend en effet que le décret relatif au contournement d'une mesure technique a été publié le 30 Décembre.
Avec la publication de ce décret la lecture d'un DVD avec un logiciel s'appuyant sur un moyen de contournement existant (comme DeCSS, utilisé par de très nombreux systèmes logiciels libres) est donc passible d'une contravention de 4ème classe. Ainsi donc lire un DVD, légalement acheté par exemple, sur un système d'exploitation libre devient un acte passible d'une amende.

Loin de pousser au boycott, je suis quand même content de n'avoir ni offert ni reçu de DVD ce Noël...

Sinon, pour 2007, j'ai migré le blog sur le nouvelle version de Blogger, qui n'est plus en Beta. Je n'ai pas perdu mon modèle, et je vais essayer d'y ajouter l'affichage des catégories/libellés, tant attendues.

Libellés : , ,