less sox

半年前から参加してる、ジジババ向けの体操教室を卒業した。記念に、歩行姿勢の評価をするので、是非参加してくださいとの事。

どんな装置でやるのかと思ったら、パソコンと Xbox One Kinect センサーと思しきやつをUSBで繋いだやつだった。結果はプリンターで印刷してくれた。

カメラに向かって、5メートル程前から普通に歩くだけのラクチン方法だった。

歩行年齢は実年齢より9歳若いと出た。細かい評価も出てた。速度年齢は年相応。バランスは-10年。姿勢は-20年だった。

悪い点は体の揺れがある事。体幹が揺れ、肩が揺れ、頭が揺れって、ヤーさんモードになってて、回りが避けて通ると判定されたぞ。昔からの癖は直らない。

歩幅は右が69cm、左が64cm。胸腹部の上下動5cmだそうだ。歩行速度は73m/sはいいんだけど、わずか5mじゃ、スピードに乗れないよ。

「歩行姿勢測定システム」

パソコンの画面を見てたら、被試験者が歩き出すと、体の各所(の関節部分)が認識されて、以後そのマーカーの動きを追跡してるみたいだ。こういうの、OpenCVでやるのかなって、現場で直感したよ。中らずと雖も遠からずだな。

これを進化させたやつが、サツに納入されてるんだろうね。オイラーなんかだと、一発で認識されちゃいそうだな。左右のバランス不良、ヤーさん歩きの人だもの。きんちゃん歩きを習得しておくかな。まて、その方がよっぽど目立つぞ。

spectrogram

前回の表題は more soxだった。もう一度soxするんで、more^2 sox ? 日本語読みすると、モヤモヤsoxとなって、ちょっとかっこ悪い。そこで、Unixに倣って、moreの次はlessでしょって、安易にタイトルを付けたよ。

前回最後に目を付けておいたパイプ。 スペクトログラムを詳細に取ろうとして、下記のようなパイプ方式を取り入れて書いてみた。

  sox $1 -t wav - trim $st $cutsize | sox -t wav - -n spectrogram -o ${st}.png

が、あろう事か、下記のような警告に遭遇した。

sox WARN wav: Length in output .wav header will be wrong since can't seek to fix it

cutsizeは30秒分を指定してるんだけど、実際は8秒分しか取得していない。この分では、どこがスタートかも分かったものじゃない。

涙を飲んで、中間ファイルを作るようにしたよ。トホホ。

#!/bin/sh
# Usage: $0 soundfile
# spectrogram each cutsize (time)

cutsize=30
hmsS=`soxi $1 | awk '/Duration/{print $3}'`
playtime=`echo $hmsS | awk -F: '{print $2 * 60 + int($3) }'`
sux=${1##*.}

st=0
while test $st -lt $playtime
do
      case $st in
          ?)  st=00$st ;;
          ??) st=0$st ;;
      esac
      echo '-->' $st
      sox $1 dummy.${sux} trim $st $cutsize
      sox dummy.${sux} -n rate 6k spectrogram -o ${st}.png
      st=`expr $st + $cutsize`
done
rm -f dummy.${sux}
ob$ soxi S.wav
  :
Duration       : 00:03:12.91 = 8507392 samples = 14468.4 CDDA sectors

192秒のファイルなんで、最後の部分は、期待に添えませんでしたと、下記のような警告が出てきた。

--> 180
sox WARN trim: End position is after expected end of audio.
sox WARN trim: Last 1 position(s) not reached.

ああ、ファイル名は3桁固定の数字名にしてる。こうしておくと、gpicviewとかWindowsのフォトを見るやつでも、クリックだけで順番に見られて好都合。

尚、声紋を精査するならcutsizeを10にして、10秒間隔のpngファイルを製作すれば良い。枚数が増えてやだって向きには、上記スクリプトをたたき台にして、自由に改変してください。

sox on many OS

soxの開発は2015年で止まっているようで、それぞれのOSでは、同じ版のものがインストールされる。同じように動くはずなんだけど、OSそれぞれの事情と言うか環境違いで、微妙な差が出て来る。

at ubuntu

i386な実機のdebianで問題無く動いていたので、ウブでも楽勝と思っていたんだ。いきなり足元がふらついていたぞ。

sakae@usvr:~/WAV$ play 15.wav
ALSA lib confmisc.c:767:(parse_card) cannot find card '0'
ALSA lib conf.c:4528:(_snd_config_evaluate) function snd_func_card_driver returned error: No such file or directory
  :

soxの別名playコマンドで演奏しようと思ったら、上記のような有様。 group add to audio するといいよって情報有り。

sakae@usvr:~/WAV$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: AudioPCI [Ensoniq AudioPCI], device 0: ES1371/1 [ES1371 DAC2/ADC]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 0: AudioPCI [Ensoniq AudioPCI], device 1: ES1371/2 [ES1371 DAC1]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

やっと認識した。でも、まだ音がしない。alsamixerしたら、コンソール卓が出て来た。マスター側とPCMのボリュームを上げるも音無しの構え。mを叩いて、ミュートを解除して、やっと音が出て来た。やれやれ。

at CentOS

cent:WAV$ play travel.mp3
play FAIL formats: no handler for file extension `mp3'
cent:WAV$ play 16.wav

16.wav:

 File Size: 34.0M     Bit Rate: 1.41M
  Encoding: Signed PCM
  Channels: 2 @ 16-bit
Samplerate: 44100Hz
Replaygain: off
  Duration: 00:03:12.91

mp3をサポートしないのは権利問題かな。それにしても、wavで音がしないのは何故? ミキサーにPCMからの入力が無くて、メインボリュームだけがある。

cent:WAV$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: AudioPCI [Ensoniq AudioPCI], device 0: ES1371/1 [ES1371 DAC2/ADC]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 0: AudioPCI [Ensoniq AudioPCI], device 1: ES1371/2 [ES1371 DAC1]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

カードは認識してるようだし

cent:WAV$ lsmod | grep snd
snd_seq_midi           13565  0
snd_seq_midi_event     14899  1 snd_seq_midi
snd_ens1371            25076  5
snd_rawmidi            31294  2 snd_ens1371,snd_seq_midi
snd_ac97_codec        130556  1 snd_ens1371
ac97_bus               12730  1 snd_ac97_codec
snd_seq                62663  2 snd_seq_midi_event,snd_seq_midi
snd_seq_device         14356  3 snd_seq,snd_rawmidi,snd_seq_midi
snd_pcm               105708  2 snd_ac97_codec,snd_ens1371
snd_timer              29912  2 snd_pcm,snd_seq
snd                    83815  17 snd_ac97_codec,snd_timer,snd_pcm,snd_seq,snd_rawmidi,snd_ens1371,snd_seq_device
soundcore              15047  1 snd
cent:WAV$ cat /proc/asound/cards
 0 [AudioPCI       ]: ENS1371 - Ensoniq AudioPCI
                      Ensoniq AudioPCI ENS1371 at 0x2080, irq 18

モジュールもちゃんとロードされてる。面倒だから封印。mixer に PulseAudioって出てるのがヒントになるのかしらん。

OpenBSD

VMwareに入れているamd64版のやつは、全く問題なく演奏出来る。vboxに入れてるi386版は、 音が飛ぶなあ。

sndiodを止めていたんで、それが影響してるかと思って生かしてみたら、更にひどい事になった。(全く転送しない)

FreeBSD

こちらもOpenBSD(i386)と同様に、音が飛ぶぞ。音はBSDにとっては鬼門なのか。まて、まだ希望の星NetBSDで試行してないじゃん。

じゃ、いつか善処してみましょ(と国会答弁風)

soxの内部監査

そんじゃ、恒例の内部監査と言うか、内部探検をしてみるか。監査場所は、全てに優秀と認められるウブの中ね。

出来合いのやつだとプローブ出来ないので、ソースを取ってきて、--enable-debug --prefix オプションを付けてコンパイルしておく。

まずは、冒頭の方で出てた、trimした時に最後の部分が指定した秒数に満たないと言った不平を並べてくれたやつ。吊るし上げします。軽くgrepをかけると、trim.cが対象になってると分かります。

(gdb) b main
Breakpoint 1 at 0xd6a7: file sox.c, line 2852.
(gdb) r 16.wav z.wav 180 30
Starting program: /home/sakae/MINE/bin/sox 16.wav z.wav 180 30
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, main (argc=5, argv=0x7fffffffe3c8) at sox.c:2852
2852    {
(gdb) b start
Breakpoint 2 at 0x7ffff7b2146e: start. (42 locations)

型通りに止めたい関数を指定したら、同名な関数が42個も有って、それらが全てbreakの対象になってしまいました。一体どこにそんなのが潜んでいるねん? 名前衝突の危機はどうやって回避してるねん?

(gdb) info break
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00005555555616a7 in main at sox.c:2852
        breakpoint already hit 1 time
2       breakpoint     keep y   <MULTIPLE>
2.1                         y     0x00007ffff7b2146e in start at bend.c:140
2.2                         y     0x00007ffff7b22732 in start at biquad.c:63
2.3                         y     0x00007ffff7b233da in start at biquads.c:160
  :
2.38                        y     0x00007ffff7b6e432 in start at vol.c:96
2.39                        y     0x00007ffff7b6feca in start at spectrogram.c:216
2.40                        y     0x00007ffff7b75911 in start at cdr.c:23
2.41                        y     0x00007ffff7b75a20 in start at cvsd-fmt.c:46
2.42                        y     0x00007ffff7b8f344 in start at sndfile.c:287

これを見ると、どうやらエフェクターにそれぞれ用意されてるみたいです。まあいい、監査を進めてみるか。

gdbは以前から何度も書いているようにソフトウェアの中を覗くオシロスコープ。出て来る波形をボーと見てても何も分からない。波形を見る時に、こんな波形が観測されるはずって仮定を立てるべし。オシロスコープの使い方を観察すれば、その人の技量が分かるってもんです。

Breakpoint 2, start (effp=0x55555576b040) at trim.c:60
60        priv_t *p = (priv_t*) effp->priv;
(gdb) bt
#0  start (effp=0x55555576b040) at trim.c:60
#1  0x00007ffff7b30d4b in sox_add_effect (chain=0x55555576c880, effp=0x55555576b040, in=0x7fffffffe1b0, out=0x55555576b208) at effects.c:157
#2  0x0000555555559fc1 in add_effect (chain=0x55555576c880, effp=0x55555576b040, in=0x7fffffffe1b0, out=0x55555576b208, guard=0x7fffffffe170) at sox.c:708
#3  0x000055555555b428 in add_effects (chain=0x55555576c880) at sox.c:1073
#4  0x000055555555df4e in process () at sox.c:1759
#5  0x0000555555562118 in main (argc=6, argv=0x7fffffffe3b8) at sox.c:3008

引数が全部表示されちゃってるので、ちょいと見苦しいけど、大体回っている所が分かった。ここからステップ実行していくと、目的とした不平を聞く(見る)事が出来た。事件は常務会で起こっているいるんじゃないぞ。現場で起こっているんだぞ、と、某湾岸警察署の青島刑事みたいに吠えてみた。

忘れないうちに、名前衝突の回避をbasic的に調べてみた。

sakae@usvr:~/MINE/bin$ nm sox | grep start
00000000002130f0 B __bss_start
00000000000050de t combiner_start
0000000000213000 D __data_start
0000000000213000 W data_start
                 w __gmon_start__
0000000000211ef0 t __init_array_start
                 U __libc_start_main@@GLIBC_2.2.5
0000000000005b05 t ostart
                 U sox_trim_clear_start
                 U sox_trim_get_start
0000000000003a50 T _start
00000000002132f8 b user_restart_eff

けど、こんな事で尻尾を出すはずは無いよな。きっと影の主役が居るのだろう、それは誰? 考えるまでもなく、libsox.so.xx.x だろう。

sakae@usvr:~/MINE/lib$ nm libsox.so.3.0.0 | egrep ' start$' | wc
     42     126    1050

42ってマジック・ナンバーを信用して良いかな。居所は分かったけど、その使い分けはどうやってるの? 問題が一つ解決すると、次なる疑問が沸いて出て来るのが科学の常であります。

man libsox

で上の疑問にどう答える? libsoxに埋め込まれているんで、libsoxのマニュアルを見ると、良い事が書いてありそうと予想を立てる。manを見るのが正統派だけど、例によってlinuxの意地悪によって、入っていない。ソースツリーにlibsox.txtが有るんで、それで十分だろう。

EFFECTS
       Each effect runs with one input and one  output  stream.   An  effect's
       implementation comprises six functions that may be called to the follow
       flow diagram:

       getopts             is called with a character string argument list for
                           the effect.

       start               is  called with the signal parameters for the input
                           and output streams.

       flow                is called with input and output data  buffers,  and
                           (by  reference)  the  input  and output data buffer

この他にも、drain、stop、killが有るとな。

drainって排出とかの意味だけど、オイラーはFETの端子を思い出すぞ。ソース、ゲート、ドレインとな。ソースから放出された電子がゲートをくぐり抜けて、ドレインへ排出される。

トランジスタも同様だな。エミッターから放出された電子はベースを経てコレクターに至る。コレクターってのは、あの切手のコレクターとかと同様ね。電子を集めるんだ。

ああ、オイラーは球(真空管)から入った口だった。フィラメントでカソードが加熱されると、電子がその周辺に沸いてくる。プレート(筒だけど)に高電圧をかけて、電子をおびき寄せる。途中に網(グリッド)があって、電子の流れを邪魔する。プレートって皿って意味だけど、電子を受け取る文字通りの皿ね。ああ、余計な事を書いちゃったわい。

LINKING
       The method of linking against libsox depends on how SoX  was  built  on
       your  system.  For  a  static build, just link against the libraries as
       normal. For a dynamic build, you should use libtool to  link  with  the
       correct  linker  flags.  See the libtool manual for details; basically,
       you use it as:
          libtool --mode=link gcc -o prog /path/to/libsox.la

こんな大事な案内も出てたぞ。いや、もっと大事なやつの案内もされてた。

exampleを色々用意したけど、example0.c,example1.cは必修事項だよと。

実験用のsoxは/home/sakae/MINEに用意したんで、libsoxはその下に入ってる。

sakae@usvr:/tmp$ cc -ggdb3 example0.c -L/home/sakae/MINE/lib -lsox
sakae@usvr:/tmp$ export LD_LIBRARY_PATH=/home/sakae/MINE/lib

この方法だと、LD_LIBRARY_PATHを、a.outを実行する時に指定しないと、既定(/usr/lib/x86_64-linux-gnu/libsox.so.3)のやつが使われてしまう。

sakae@usvr:/tmp$ export LD_RUN_PATH=/home/sakae/MINE/lib
sakae@usvr:/tmp$ cc -ggdb3 example0.c -L/home/sakae/MINE/lib -lsox

この方法だと、コンパイル時にライブラリィーのPATHが埋め込まれる(自由度は無くなる)。

上記は、example0.cをsoxのソースツリー外に持ってきているので、sox.hが不足しててエラーになる。sox.hもexample0.cの所にコピーしておいてね。

初めてのsoxプログラミング

example0.cの挙動を想像します。ソースをざっと見。

/*
 * Reads input file, applies vol & flanger effects, stores in output file.
 * E.g. example0 monkey.au monkey.aiff
 */

volを調整してflangerと言う効果をかけた出力ファイルを作成するアプリの例。

infile  input  buf1  vol  buf2  flanger  buf3  output  outfile
        -----        ---        -------        ------

全体はこんなイメージになるんじゃなかろうか。下線を引いたやつが、エフェクターだろうね。 指定された入力ファイルからinputエフェクトを使って、buf1にデータ読み込み。そのデータbuf1を入力として音量調整。結果はbuf2に入る。以下、flangerして、最後に出力ファイルにoutput。

データは、パイプを流れていく間に、色々な操作が行われるとな。

sakae@usvr:/tmp/hoge$ gdb -q a.out
Reading symbols from a.out...done.
(gdb) b start
Function "start" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (start) pending.
(gdb) r 16.wav z.wav
Starting program: /tmp/hoge/a.out 16.wav z.wav
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, start (effp=0x55555575a780) at vol.c:96
96          priv_t * vol = (priv_t *) effp->priv;
(gdb) bt
#0  start (effp=0x55555575a780) at vol.c:96
#1  0x00007ffff7b30d4b in sox_add_effect (chain=0x555555759f70,
    effp=0x55555575a780, in=0x5555557572f8, out=0x5555557572f8)
    at effects.c:157
#2  0x0000555555554e25 in main (argc=3, argv=0x7fffffffe418) at example0.c:70
(gdb) c
Continuing.

Breakpoint 1, start (effp=0x55555575a780) at flanger.c:119
119       priv_t * f = (priv_t *) effp->priv;
(gdb) bt
#0  start (effp=0x55555575a780) at flanger.c:119
#1  0x00007ffff7b30d4b in sox_add_effect (chain=0x555555759f70,
    effp=0x55555575a780, in=0x5555557572f8, out=0x5555557572f8)
    at effects.c:157
#2  0x0000555555554ecb in main (argc=3, argv=0x7fffffffe418) at example0.c:77
(gdb) c
Continuing.
[Inferior 1 (process 1950) exited normally]

上記はstartに注目して追いかけたけど、それがどう登録されてるかのヒントが、前段階であるみたいだ。(匂いがすると言う、野生の勘)

    /* Create the `vol' effect, and initialise it with the desired parameters:  */
  =>e = sox_create_effect(sox_find_effect("vol"));

上記はmainにある呼び出し側。エフェクトを指定して、そんなの有るか探し出している。

effects.c それの処理部分

static sox_effect_fn_t s_sox_effect_fns[] = {
#define EFFECT(f) lsx_##f##_effect_fn,
#include "effects.h"
#undef EFFECT
  NULL
};

const sox_effect_fn_t*
sox_get_effect_fns(void)
{
    return s_sox_effect_fns;
}

/* Find a named effect in the effects library */
sox_effect_handler_t const * sox_find_effect(char const * name)
{
  int e;
=>sox_effect_fn_t const * fns = sox_get_effect_fns();
  for (e = 0; fns[e]; ++e) {
    const sox_effect_handler_t *eh = fns[e] ();
    if (eh && eh->name && strcasecmp(eh->name, name) == 0)
      return eh;                 /* Found it. */
  }
  return NULL;
}

登録されてる例。

(gdb) p s_sox_effect_fns[2]
$1 = (sox_effect_fn_t) 0x7ffff7b24e04 <lsx_bandpass_effect_fn>
(gdb) p s_sox_effect_fns[15]
$2 = (sox_effect_fn_t) 0x7ffff7b2e44a <lsx_dither_effect_fn>

そして、sox_create_effect(.."vol")の返値

(gdb) p eh
$3 = (const sox_effect_handler_t *) 0x7ffff7dd19a0 <handler.5754>
(gdb) p *eh
$4 = {
  name = 0x7ffff7bb5ca1 "vol",
  usage = 0x7ffff7bb5ca8 "GAIN [TYPE [LIMITERGAIN]]\n\t(default TYPE=amplitude:\
 1 is constant, < 0 change phase;\n\tTYPE=power 1 is constant; TYPE=dB: 0 is co\
nstant, +6 doubles ampl.)\n\tThe peak limiter has a gain much less than 1 "...,\

  flags = 144,
  getopts = 0x7ffff7b6e116 <getopts>,
  start = 0x7ffff7b6e42a <start>,
  flow = 0x7ffff7b6e48b <flow>,
  drain = 0x0,
  stop = 0x7ffff7b6e752 <stop>,
  kill = 0x0,
  priv_size = 48
}

これらの元データは、libsox.soの中に有るはず。readelf -s を使って探りを入れてみる。 どのファイルに定義されてたって情報が付いてた。だから、同名な関数でも区別出来るのね。

Symbol table '.symtab' contains 2564 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
   999: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS vol.c
  1000: 000000000006f0cc    37 FUNC    LOCAL  DEFAULT   12 lsx_readsb
  1001: 000000000006f0f1    37 FUNC    LOCAL  DEFAULT   12 lsx_readsw
  1002: 00000000002ce860    64 OBJECT  LOCAL  DEFAULT   19 vol_types
  1003: 000000000006f116   788 FUNC    LOCAL  DEFAULT   12 getopts
  1004: 000000000006f42a    97 FUNC    LOCAL  DEFAULT   12 start
  1005: 000000000006f48b   711 FUNC    LOCAL  DEFAULT   12 flow
  1006: 000000000006f752   204 FUNC    LOCAL  DEFAULT   12 stop
  1007: 00000000002d29a0    80 OBJECT  LOCAL  DEFAULT   23 handler.5754

こちらは、vol.cをコンパイルした結果。こういう複数のオブジェクトファイルを寄せ集めたやつが、soファイルなんだな。そー言う事だ。

sakae@usvr:~/sox-14.4.2/src/.libs$ nm libsox_la-vol.o
                 U exp
00000000000003bf t flow
000000000000004a t getopts
                 U _GLOBAL_OFFSET_TABLE_
0000000000000000 d handler.5754
                 U __isoc99_sscanf
                 U lsx_debug_impl
                 U lsx_find_enum_text
                 U lsx_readb
0000000000000000 t lsx_readsb
0000000000000025 t lsx_readsw
                 U lsx_readw
                 U lsx_usage
0000000000000752 T lsx_vol_effect_fn
                 U lsx_warn_impl
                 U sox_get_globals
                 U sqrt
                 U __stack_chk_fail
000000000000035e t start
0000000000000686 t stop
0000000000000000 d vol_types

ここまではいいんだけど、このテーブルを本体側に取り込む手法が分からん。リンカーローダー本にでも解説されてるかな?

まて、早まるな。ソースを穴のあくほど眺めるんだ。そしたら、見えてきた。どんなハンドラーかってのが、決まった命名規則で、それぞれのファイルに登録されてた。例を2つ挙げておく。

vol.c

sox_effect_handler_t const * lsx_vol_effect_fn(void)
{
  static sox_effect_handler_t handler = {
    "vol", vol_usage, SOX_EFF_MCHAN | SOX_EFF_GAIN, getopts, start, flow, 0, stop, 0, sizeof(priv_t)
  };
  return &handler;
}

trim.c

sox_effect_handler_t const *lsx_trim_effect_fn(void)
{
  static sox_effect_handler_t handler = {
    "trim", "{position}",
    SOX_EFF_MCHAN | SOX_EFF_LENGTH | SOX_EFF_MODIFY,
    parse, start, flow, drain, NULL, lsx_kill,
    sizeof(priv_t)
  };
  return &handler;
}

おまけの example3.c

/*
 * On an alsa capable system, plays an audio file starting 10 seconds in.
 * Copes with sample-rate and channel change if necessary since its
 * common for audio drivers to support a subset of rates and channel
 * counts.
 * E.g. example3 song2.ogg
 *
 * Can easily be changed to work with other audio device drivers supported
 * by libSoX; e.g. "oss", "ao", "coreaudio", etc.
 * See the soxformat(7) manual page.
 */


  assert(in = sox_open_read(argv[1], NULL, NULL, NULL));
  /* Change "alsa" in this line to use an alternative audio device driver: */
  assert(out= sox_open_write("default", &in->signal, NULL, "alsa", NULL, NULL));

直接、デバイスファイルを叩いて音を鳴らすのは不作法です。礼儀正しい振る舞いをお願いしますとな。

こんな方法で、playとかrecが出来ているんだね。

where we go

と、何回かに渡って、soxを見てきた。これから深掘りしてってもいいんだけど、余り粘着すると、井の中の蛙になっちゃうな。

音関係の有名なアプリとして、audacityなんてのがある。debian機を作ったとき、記念にいれた。GUIで音を切った貼ったするのに適していそう。音屋さんには便利かも。

今回タイトルに付けている、less sox だけど、語順を逆にすると、sox lessになる。 soxを排除って事だな。そろそろ潮時か。

ああ、lessにまつわる面白い小話を思い出した。

かの昔BASICが全盛の頃、goto文であちこち飛び回るものだから、書いた本人さえ解読不能になる、スパゲッティプログラムが量産された。

これに業を煮やしたダイクストラ先生が、構造化プログラミングを提唱された。分かり易い言い方をすると、goto less にしましょって訳。

たまたま、ダイクストラ先生が講演されてる会議に、日本のエース後藤先生が出席された。

ダイクストラさん、余りgoto less て言わんでください。日本では、後藤 less に聞こえますから。