URL: https://linuxfr.org/users/vmagnin/journaux/taptempo-fortran Title: TapTempo Fortran Authors: vmagnin Date: 2021年11月01日T11:25:59+01:00 License: CC By-SA Tags: taptempo et fortran Score: 30 # TapTempo Fortran Difficile d'échapper à l'engouement planétaire pour TapTempo... Voici donc enfin une version en Fortran moderne (ce qui pour moi désigne le langage à partir de la norme Fortran 90). ## Pourquoi Fortran ? 1. Le calcul éminemment scientifique du tempo me semble mériter de recourir au King du calcul, le _mathematical FORmula TRANslating system_ de John Backus, [prix Turing 1977](https://amturing.acm.org/award_winners/backus_0703524.cfm). 2. Fortran a commencé sa carrière en même temps qu'Elvis Presley, en 1954 (année de début du projet, le premier compilateur ayant été déployé chez les clients d'IBM en 1957). ## Le code source Le projet est disponible sous licence GNU GPLv3 ici : https://github.com/vmagnin/TapTempo-Fortran Il utilise pour sa construction le _Fortran Package Manager_ fpm (https://github.com/fortran-lang/fpm) développé depuis deux ans par la communauté Fortran-lang, mais vous pouvez aussi le compiler simplement avec la commande : ```bash $ gfortran -o taptempo src/taptempo.f90 app/main.f90 ``` ou si vous êtes pressé, copier/coller le code source suivant dans un unique fichier `taptempo.f90` et le compiler avec `gfortran -o taptempo taptempo.f90` (gfortran fait partie de la GCC) : ```fortran module taptempo ! Reals working precision and 64 bits integers: use, intrinsic :: iso_fortran_env, only: wp=>real64, int64 implicit none ! Tap Tempo Version: character(len=*), parameter :: version = "1.0.0" ! Default values of the command options: integer :: s = 5 ! Stack size integer :: p = 0 ! Precision integer :: r = 5 ! Reset time in seconds logical :: out_flag = .false. ! Flag for the ouput file private public :: manage_command_line, measure_tempo contains subroutine print_version print '(A)', "TapTempo Fortran "//version print '(A)', "Copyright (C) 2021 Vincent Magnin and the Fortran-lang community" print '(A)', "License GPLv3+: GNU GPL version 3 or later " print '(A)', "This is free software: you are free to change and redistribute it." print '(A)', "There is NO WARRANTY, to the extent permitted by law." end subroutine subroutine print_options print '(A)' print '(A)', "Usage: taptempo [options]" print '(A)' print '(A)', "Options :" print '(A)', " -h, --help display this help message" print '(A)', " -o, --output save the results in the taptempo.txt file" print '(A)', " -p, --precision change the number of decimals for the tempo," print '(A)', " the default is 0 decimal places, the max is 5 decimals" print '(A)', " -r, --reset-time change the time in seconds to reset the calculation," print '(A)', " the default is 5 seconds" print '(A)', " -s, --sample-size change the number of samples needed to calculate the tempo," print '(A)', " the default is 5 samples, the minimum is 2" print '(A)', " -v, --version print the version number" print '(A)' print '(A)', "Home page: " end subroutine subroutine manage_command_line integer :: i, nb, status character(len=100) :: args nb = command_argument_count() if (nb == 0) return i = 0 do while (i < nb) i = i + 1 call get_command_argument(i, value=args) select case(trim(args)) case("-h", "--help") call print_version() call print_options() stop case("-o", "--output") out_flag = .true. case("-p", "--precision") i = i + 1 call get_command_argument(i, value=args) read(args, *, iostat=status) p if (status /= 0) print '(A)', "Problem with -p: the default value will be used" p = max(0, min(p, 5)) ! 0 <= p <= 5 case("-r", "--reset-time") i = i + 1 call get_command_argument(i, value=args) read(args, *, iostat=status) r if (status /= 0) print '(A)', "Problem with -r: the default value will be used" case("-s", "--sample-size") i = i + 1 call get_command_argument(i, value=args) read(args, *, iostat=status) s if (status /= 0) print '(A)', "Problem with -s: the default value will be used" s = max(2, s) case("-v", "--version") call print_version() stop case default print '(2A)', "Unknown option ignored: ", trim(args) end select end do end subroutine manage_command_line subroutine measure_tempo character(len=1) :: key integer(int64) :: count ! Count of the processor clock real(wp) :: rate ! Number of clock ticks per second integer :: i integer :: my_file ! Unit of the output file real(wp), dimension(s) :: t ! Time FIFO stack real(wp) :: t0 ! Time origin real(wp) :: bpm ! Beats Per Minute integer :: oldest character(len=28) :: fmt ! Format used for printing the tempo: write(fmt, '(A, I1, A)') '("Tempo: ", f10.', p, ', " BPM ")' ! Stack initialization: t = 0 if (out_flag) then open(newunit=my_file, file="taptempo.txt", status="replace") write(my_file, '(A)') "# t bpm" end if print '(A)', "Hit Enter key for each beat (q to quit)." ! Infinite loop: i = 0 do ! Reading the standard input: read '(a1)', key if (key == 'q') exit call system_clock(count, rate) ! Updating the time FIFO stack: t(2:s) = t(1:s-1) t(1) = count / rate i = i + 1 if (i == 1) then print '(A)', "[Hit enter key one more time to start BPM computation...]" t0 = t(1) else ! Verify that the user is actively tapping: if (t(1) - t(2) <= r) then ! Oldest time in the stack: oldest = min(i, s) ! Computes and prints the beats per minute: bpm = 60 / ((t(1) - t(oldest)) / (oldest - 1)) write(*, fmt, advance="no") bpm if (out_flag) write(my_file, '(F9.3, F12.5)') t(1)-t0, bpm else print '(A)', "Time reset" i = 1 t(2:s) = 0 end if end if end do print '(A)', "I don't know why you say goodbye..." if (out_flag) close(my_file) end subroutine measure_tempo end module taptempo program main use taptempo, only: manage_command_line, measure_tempo implicit none call manage_command_line() call measure_tempo() end program main ``` ## Commentaires sur le code source Le programme principal se contente d'appeler la procédure qui gère la ligne de commandes et la procédure qui mesure le tempo. Ces procédures sont placées dans le module `taptempo` : les modules ont été introduits par la norme Fortran 90 (la norme Fortran 2008 a même introduit les _submodules_ pour les grands codes). Fortran utilise pour les types de données son système de `KIND` (un numéro de type de données) : avant Fortran 2008, on aurait par exemple écrit `real(8)` pour espérer avoir un réel sur 8 octets, malheureusement ce n'était pas normalisé et le nombre entre parenthèses n'était pas forcément le nombre d'octets dans certains compilateurs... Le module intrinsèque `iso_fortran_env` définit donc désormais des constantes pour les KINDs de chaque grand type de données, par exemple `real64` (renommé `wp` dans notre programme) et `int64`. Depuis Fortran 2003, on peut gérer les arguments de la ligne de commandes avec `command_argument_count()` et `get_command_argument()`. Les chaînes de caractères Fortran étant de taille fixe (c'est plus rapide !), on utilise la commande `trim()` qui supprime les espaces excédentaires. La commande `read(args, *, iostat=status) p` utilise la chaîne `args` au lieu de l'entrée standard pour écrire la valeur du paramètre dans l'entier p. La variable `status` reçoit un code d'erreur non nul en cas de problème. La ligne `write(fmt, '(A, I1, A)') '("Tempo: ", f10.', p, ', " BPM ")'` permet au contraire d'écrire des données dans la chaîne `fmt` en respectant le format `'(A, I1, A)'` (une chaîne, un entier sur un caractère, une chaîne). Pour l'écriture des résultats dans l'éventuel fichier de sortie, on a besoin de réserver une _unité_ non utilisée (`newunit=my_file`). Traditionnellement, les unités d'entrées/sorties standards occupent les unités 5 et 6 et peuvent être représentées par une étoile, mais on peut désormais aussi utiliser les constantes `input_unit` et `output_unit` définies dans le module `iso_fortran_env`. Depuis Fortran 90, le langage permet de manipuler facilement des tableaux : `t = 0` permet d'initialiser à zéro tous les éléments du tableau. Faire avancer la pile FIFO représentée par ce tableau se fait simplement : `t(2:s) = t(1:s-1)`. Enfin, la commande `call system_clock(count, rate)` disponible depuis Fortran 90 permet de récupérer le nombre de tics de l'horloge système et son nombre de tics par secondes. A noter que Fortran dispose aussi de la fonction `cpu_time()`, mais pendant que l'instruction `read` attend, le processus est _idle_ et la valeur de cette fonction est donc figée. `cpu_time()` est avant tout conçue pour déterminer le temps de calcul, cheval de bataille du langage. Ces particularités du langage étant présentées, le reste du code devrait être relativement lisible pour un développeur qui n'est pas habitué au Fortran. ## Limitations * Il n'y a pas d'internationalisation, il parle la langue de John Backus et d'Elvis Presley. A noter que pour afficher les réels avec une virgule au lieu d'un point décimal, il suffirait d'ajouter dans le format `dc,` _(decimal comma)_ avant le `f10.` Seul inconvénient, si p vaut zéro, ça affichera la virgule quand même, ce qui ne passe pas en Français. * Si on garde la touche Entrée enfoncée, ça plafonne à 1500 BPM... Probablement une limitation du terminal puisque la version C++ plafonne à la même valeur. Dommage, on aurait pu espérer s'approcher du GBPM. * Si le système reste allumé très longtemps, le nombre de tics de l'horloge finira par revenir à zéro. Mais avec des entiers 64 bits on pourra aller jusque 2**63-1 = +9223372036854775807. Avec GFortran sur un PC actuel, l'horloge génère un milliard de tics par seconde. On devrait tenir 9223372036 secondes ~ 106751 jours ~ 292 ans. De quoi nous prémunir du [bug des missiles Patriot](https://www-users.cse.umn.edu/~arnold/disasters/patriot.html). Vous pouvez donc même utiliser TapTempo sur un serveur ! ## Perspectives * Vous pouvez utiliser TapTempo pour mesurer votre rythme cardiaque : mettez une main autour de votre gorge et tapez en rythme sur Entrée. Ne serrez pas ! Rappelons la clause 16 de la GNU GPL v3 : __16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM...)_ * On pourrait créer une interface graphique, par exemple avec [gtk-fortran](https://github.com/vmagnin/gtk-fortran), pour remplacer la touche Entrée par un clic de souris sur un bouton. Pour rester minimaliste, le tempo pourrait être affiché comme texte du bouton.

AltStyle によって変換されたページ (->オリジナル) /