Sommaire
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 ?
- 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.
- 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 :
$ 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) :
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 <http://gnu.org/licenses/gpl.html>"
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: <https://github.com/vmagnin/TapTempo-Fortran>"
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 lef10.
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. 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, 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.
# Erratum
Posté par vmagnin (site web personnel) . Évalué à 6.
Attention, si vous copiez-collez le code source dans un fichier unique, il faut mettre le
program main
tout à la fin, après le module. Mais je ne peux pas corriger mon message initial (c'est curieux, d'après mes souvenirs il y avait un bouton Modifier avant, qui permettait en tout cas d'éditer son message dans les 5 minutes).[^] # Re: Erratum
Posté par gUI (Mastodon) . Évalué à 6.
J'ai corrigé, ça va comme ça ?
En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.
[^] # Re: Erratum
Posté par vmagnin (site web personnel) . Évalué à 3.
Merci, oui c'est bon !
# Qui dit mieux ?
Posté par Joalland . Évalué à 5. Dernière modification le 01 novembre 2021 à 14:50.
Quand je maintiens sur la touche entrée j'ai :
Tempo: 1989. BPM
Tempo: 1995. BPM
Tempo: 1991. BPM
Tempo: 1986. BPM
Tempo: 1997. BPM
Tempo: 1973. BPM
Tempo: 1959. BPM
Tempo: 1959. BPM
Tempo: 1978. BPM
Tempo: 1973. BPM
Tempo: 1998. BPM
Tempo: 1997. BPM
Tempo: 1980. BPM
Tempo: 1989. BPM
Tempo: 1969. BPM
Tempo: 1976. BPM
Tempo: 1972. BPM
Tempo: 1976. BPM
Tempo: 1982. BPM
Tempo: 1982. BPM
Tempo: 1985. BPM
Tempo: 1984. BPM
Tempo: 1986. BPM
Tempo: 1976. BPM
Tempo: 1983. BPM
Tempo: 1951. BPM
Qui peut faire plus haut ?
[^] # Re: Qui dit mieux ?
Posté par kp . Évalué à 10. Dernière modification le 01 novembre 2021 à 15:02.
J'imagine que ça dépend surtout de l'intervalle de répétition du clavier.
Avec le clavier :
Sans le clavier :
[^] # Re: Qui dit mieux ?
Posté par vmagnin (site web personnel) . Évalué à 10.
Bien vu, j'arrive à 6000 BPM en réglant la répétition du clavier à 100 rép/s dans la configuration de KDE. La valeur par défaut y est de 25 rép/s (et on a bien 25*60=1500).
La science autour de TapTempo avance !
# Compétition internationale
Posté par Guillawme (site web personnel, Mastodon) . Évalué à 10.
Bon, pour le moment la France a une longueur d’avance avec toutes ces recherches sur TapTempo publiées dans ce journal et les précédents, mais il commence à y avoir de la compétition internationale : https://lobste.rs/s/dyxogq/bpm_is_tap_along_with_beat_song_discover
Si quelqu’un a un compte sur lobster.rs, ce serait marrant de montrer à ce type toutes les implémentations déjà postées dans des journaux ici.
[^] # Re: Compétition internationale
Posté par vmagnin (site web personnel) . Évalué à 10. Dernière modification le 01 novembre 2021 à 19:29.
Il est clairement URGENT de relancer la TapTempo Federation ! Elle semble à l'abandon, il y a 26 Issues ouvertes, beaucoup datant même de mars 2018 :
https://github.com/TapTempo-Federation/TapTempo-Federation/issues
Voilà qui explique les nombreuses difficultés rencontrées par la France au niveau international ces derniers temps. Il est encore temps d'inverser la vapeur.
Rappelons d'ailleurs l'article 1 de son manifeste :
[^] # Re: Compétition internationale
Posté par Chris K. . Évalué à 3.
Effectivement il y a urgence, mettre en avant l'excellence linuxfrienne en matière de rythme, ca n'arrive pas tous les siècles.
Je plaide donc pour la création d'une internationale du tap tempo avec charte éthique, hymne et drapeau.
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.