metro.pp 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. {
  2. Copyright (C) 2002 Anthony Van Groningen
  3. This program is free software; you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation; either version 2 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program; if not, write to the Free Software
  13. Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  14. }
  15. program metro;
  16. uses
  17. CTypes, SysUtils, Jack;
  18. type
  19. Psample_t = ^sample_t;
  20. sample_t = jack_default_audio_sample_t;
  21. var
  22. client: Pjack_client_t;
  23. output_port: Pjack_port_t;
  24. sr: culong;
  25. freq: cint = 880;
  26. bpm: cint;
  27. tone_length, wave_length: jack_nframes_t;
  28. wave: Psample_t;
  29. offset: clong = 0;
  30. transport_aware: Boolean = False;
  31. transport_state: jack_transport_state_t;
  32. procedure usage;
  33. begin
  34. Writeln(StdErr);
  35. Writeln(StdErr, 'usage: jack_metro ');
  36. Writeln(StdErr, ' [ --frequency OR -f frequency (in Hz) ]');
  37. Writeln(StdErr, ' [ --amplitude OR -A maximum amplitude (between 0 and 1) ]');
  38. Writeln(StdErr, ' [ --duration OR -D duration (in ms) ]');
  39. Writeln(StdErr, ' [ --attack OR -a attack (in percent of duration) ]');
  40. Writeln(StdErr, ' [ --decay OR -d decay (in percent of duration) ]');
  41. Writeln(StdErr, ' [ --name OR -n jack name for metronome client ]');
  42. Writeln(StdErr, ' [ --transport OR -t transport aware ]');
  43. Writeln(StdErr, ' --bpm OR -b beats per minute');
  44. end;
  45. procedure process_silence (nframes: jack_nframes_t); cdecl;
  46. var
  47. buffer: Psample_t;
  48. begin
  49. buffer := jack_port_get_buffer (output_port, nframes);
  50. FillChar (buffer^, SizeOf (jack_default_audio_sample_t) * nframes, 0);
  51. end;
  52. procedure process_audio (nframes: jack_nframes_t); cdecl;
  53. var
  54. buffer: Psample_t;
  55. frames_left: jack_nframes_t;
  56. begin
  57. buffer := jack_port_get_buffer (output_port, nframes);
  58. frames_left := nframes;
  59. while (wave_length - offset) < frames_left do
  60. begin
  61. Move((wave + offset)^, (buffer + (nframes - frames_left))^, SizeOf (sample_t) * (wave_length - offset));
  62. frames_left -= wave_length - offset;
  63. offset := 0;
  64. end;
  65. if frames_left > 0 then
  66. begin
  67. Move((wave + offset)^, (buffer + (nframes - frames_left))^, SizeOf (sample_t) * frames_left);
  68. offset += frames_left;
  69. end;
  70. end;
  71. function process (nframes: jack_nframes_t; arg: Pointer): cint; cdecl;
  72. var
  73. pos: jack_position_t;
  74. begin
  75. if transport_aware then
  76. begin
  77. if (jack_transport_query (client, @pos) <> JackTransportRolling) then
  78. begin
  79. process_silence (nframes);
  80. Exit(0);
  81. end;
  82. offset := pos.frame mod wave_length;
  83. end;
  84. process_audio (nframes);
  85. Result := 0;
  86. end;
  87. function sample_rate_change: cint; cdecl;
  88. begin
  89. Writeln('Sample rate has changed! Exiting...');
  90. Halt(-1);
  91. end;
  92. var
  93. scale: sample_t;
  94. i, attack_length, decay_length: cint;
  95. amp: Pcdouble;
  96. max_amp: cdouble = 0.5;
  97. option_index: cint = 0;
  98. got_bpm: Boolean = false;
  99. bpm_string: string = 'bpm';
  100. attack_percent: cint = 1;
  101. decay_percent: cint = 10;
  102. dur_arg: cint = 100;
  103. client_name: string = '';
  104. verbose: Boolean = False;
  105. status: jack_status_t;
  106. code: Integer;
  107. begin
  108. while option_index < ParamCount do
  109. begin
  110. Inc(option_index);
  111. case ParamStr(option_index) of
  112. '-f', '--frequency':
  113. begin
  114. Inc(option_index);
  115. Val(ParamStr(option_index), freq, code);
  116. if code <> 0 then
  117. begin
  118. Writeln (StdErr, 'invalid frequency');
  119. Halt(-1);
  120. end;
  121. end;
  122. '-A', '--amplitude':
  123. begin
  124. Inc(option_index);
  125. Val(ParamStr(option_index), max_amp, code);
  126. if (code <> 0) or (max_amp <= 0) or (max_amp > 1) then
  127. begin
  128. Writeln (StdErr, 'invalid amplitude');
  129. Halt(-1);
  130. end;
  131. end;
  132. '-D', '--duration':
  133. begin
  134. Inc(option_index);
  135. dur_arg := StrToInt (ParamStr(option_index));
  136. Writeln (StdErr, 'durarg = ', dur_arg);
  137. end;
  138. '-a', '--attack':
  139. begin
  140. Inc(option_index);
  141. Val(ParamStr(option_index), attack_percent, code);
  142. if (code <> 0) or (attack_percent < 0) or (attack_percent > 100) then
  143. begin
  144. Writeln (StdErr, 'invalid attack percent');
  145. Halt(-1);
  146. end;
  147. end;
  148. '-d', '--decay':
  149. begin
  150. Inc(option_index);
  151. Val(ParamStr(option_index), decay_percent, code);
  152. if (code <> 0) or (decay_percent < 0) or (decay_percent > 100) then
  153. begin
  154. Writeln (StdErr, 'invalid decay percent');
  155. Halt(-1);
  156. end;
  157. end;
  158. '-b', '--bpm':
  159. begin
  160. got_bpm := True;
  161. Inc(option_index);
  162. Val(ParamStr(option_index), bpm, code);
  163. if code <> 0 then
  164. begin
  165. Writeln (StdErr, 'invalid bpm');
  166. Halt(-1);
  167. end;
  168. bpm_string := IntToStr(bpm) + '_bpm';
  169. end;
  170. '-n', '--name':
  171. begin
  172. Inc(option_index);
  173. client_name := ParamStr(option_index);
  174. end;
  175. '-v', '--verbose':
  176. verbose := True;
  177. '-t', '--transport':
  178. transport_aware := True;
  179. '-h', '--help':
  180. begin
  181. usage;
  182. Halt(-1);
  183. end;
  184. else
  185. begin
  186. Writeln (StdErr, 'unknown option ', ParamStr(option_index));
  187. usage;
  188. Halt(-1);
  189. end;
  190. end;
  191. end;
  192. if not got_bpm then
  193. begin
  194. Writeln (StdErr, 'bpm not specified');
  195. usage;
  196. Halt(-1);
  197. end;
  198. { Initial Jack setup, get sample rate }
  199. if client_name = '' then
  200. client_name := 'metro';
  201. client := jack_client_open (PChar(client_name), JackNoStartServer, @status);
  202. if client = nil then
  203. begin
  204. Writeln (StdErr, 'jack server not running?');
  205. Halt(1);
  206. end;
  207. jack_set_process_callback (client, @process, nil);
  208. output_port := jack_port_register (client, PChar(bpm_string), JACK_DEFAULT_AUDIO_TYPE, Ord(JackPortIsOutput), 0);
  209. sr := jack_get_sample_rate (client);
  210. { setup wave table parameters }
  211. wave_length := 60 * sr div bpm;
  212. tone_length := sr * dur_arg div 1000;
  213. attack_length := tone_length * attack_percent div 100;
  214. decay_length := tone_length * decay_percent div 100;
  215. scale := 2 * PI * freq / sr;
  216. if tone_length >= wave_length then
  217. begin
  218. Writeln (StdErr, 'invalid duration (tone length = ', tone_length,
  219. ', wave length = ', wave_length);
  220. Halt(-1);
  221. end;
  222. if (attack_length + decay_length) > cint(tone_length) then
  223. begin
  224. Writeln (StdErr, 'invalid attack/decay');
  225. Halt(-1);
  226. end;
  227. { Build the wave table }
  228. wave := GetMem (wave_length * SizeOf(sample_t));
  229. amp := GetMem (tone_length * SizeOf(cdouble));
  230. for i := 0 to attack_length - 1 do
  231. amp[i] := max_amp * i / (cdouble(attack_length));
  232. for i := attack_length to cint(tone_length) - decay_length - 1 do
  233. amp[i] := max_amp;
  234. for i := cint(tone_length) - decay_length to cint(tone_length) - 1 do
  235. amp[i] := - max_amp * (i - cdouble(tone_length)) / cdouble(decay_length);
  236. for i := 0 to cint(tone_length) - 1 do
  237. wave[i] := amp[i] * sin (scale * i);
  238. for i := tone_length to cint(wave_length) - 1 do
  239. wave[i] := 0;
  240. if jack_activate (client) <> 0 then
  241. begin
  242. Writeln (StdErr, 'cannot activate client');
  243. Halt(1);
  244. end;
  245. while True do
  246. sleep(1000);
  247. end.