1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
|
<!-- <?xml version="1.0" ?>
<!DOCTYPE chapter PUBLIC "-//KDE//DTD DocBook XML V4.2-Based Variant V1.1//EN" "dtd/kdex.dtd">
To validate or process this file as a standalone document, uncomment
this prolog. Be sure to comment it out again when you are done -->
<chapter id="arts-in-detail">
<title
>&arts; в деталях</title>
<sect1 id="architecture">
<title
>Архитектура</title>
<mediaobject>
<imageobject>
<imagedata fileref="arts-structure.png" format="PNG"/>
</imageobject>
<textobject
><phrase
>Структура &arts;.</phrase
></textobject>
</mediaobject>
</sect1>
<sect1 id="modules-ports">
<title
>Модули и порты</title>
<para
>Основная идея &arts; - это синтез звука при помощи небольших модулей, которые предназначены только для этой цели, и комбинирования из них различных структур. Модули обычно имеют входы, на которые поступают различные сигналы или параметры, и выходы, на которых вырабатываются некоторые сигналы. </para>
<para
>В случае одного модуля (Synth_ADD) берутся два сигнала с входа и складываются вместе. Результатом является сигнал на выходе. Места, где модуль предоставляет свои входные/выходные сигналы, называются портами. </para>
</sect1>
<sect1 id="structures">
<title
>Структуры</title>
<para
>Структура - это набор связанных модулей, некоторые из которых могут иметь параметры, кодируемые непосредственно в их входные порты, другие могут быть связаны с остальными или вообще ни с чем не связаны. </para>
<para
>С помощью &arts-builder; вы можете описать структуры. Вы описываете, какие модули вы хотели бы соединить. Завершив описание, вы можете сохранить полученную структуру в файле или создать описанную структуру, запустив Execute. </para>
<para
>Вы услышите звук, если всё сделали правильно. </para>
</sect1>
<!-- TODO
<sect1 id="streams">
<title
>Streams</title>
</sect1>
-->
<sect1 id="latency">
<title
>Задержки</title>
<sect2 id="what-islatency">
<title
>Что такое задержки?</title>
<para
>Предположим, у вас есть программа <quote
>mousepling</quote
>, которая издает звук, если вы нажимаете на кнопку. Задержка - это время между нажатием кнопки и тем, когда вы слышите звук. Задержка в данном случае составляется из других задержек, имеющих различные причины. </para>
</sect2>
<sect2 id="latenbcy-simple">
<title
>Задержка в простых программах</title>
<para
>В этой простой программе задержки происходят в следующих местах: </para>
<itemizedlist>
<listitem>
<para
>Время, пока ядро операционной системы не оповестит сервер X11, что кнопка мыши была нажата. </para>
</listitem>
<listitem>
<para
>Время, пока сервер X11 не оповестит вашу программу, что кнопка мыши была нажата. </para>
</listitem>
<listitem>
<para
>Время, пока ваша программа не решит, что нужно проиграть звук. </para>
</listitem>
<listitem>
<para
>Время указания программой звуковому серверу, что сервер должен проиграть звук. </para>
</listitem>
<listitem>
<para
>Время прохода звукового фрагмента через буфер данных (когда звуковой сервер начинает микшировать другие потоки в один), пока он действительно не достигнет места для проигрывания звуковой картой. </para>
</listitem>
<listitem>
<para
>Время прохождения звука от колонок до вашего уха. </para>
</listitem>
</itemizedlist>
<para
>Первые три причины задержки не относятся к &arts;. Они интересны, но выходят за область этого документа. Тем не менее нужно осознавать, что они существуют, так что даже если вы всё оптимизировали до действительно низких значений, вы можете не получить в точности расчетный результат. </para>
<para
>Указание серверу проиграть что-нибудь обычно включает один вызов &MCOP;. Имеются тесты, которые подтверждают, что на одном и том же компьютере с unix-сокетами проигрывание сервером чего-нибудь возможно около 9000 раз в секунду в текущей реализации. Я предполагаю, что большая часть этой задержки создается ядром ОС при переключении между процессами. Конечно, это значение зависит от типа параметров. Если вы передаёте целый образ за один вызов, то это будет медленнее, чем если вы передадите только одно длинное значение. Это справедливо и для кода возврата. Конечно, для обычных строк (таких как имя воспроизводимого <literal role="extension"
>wav</literal
> файла) это не должно быть проблемой. </para>
<para
>Мы можем оценить это время как 1/9000 с, что меньше 0,15 мс. Мы увидим, что это не так важно. </para>
<para
>Следующая задержка - это время между началом проигрывания сервером и временем прихода фрагмента на звуковую плату. Серверу необходима буферизация, так что когда запущены другие программы, как ваш сервер X11 или программа <quote
>mousepling</quote
>, звук не прерывается. Под &Linux; создаются фрагменты определенного размера. Сервер постоянно заполняет фрагменты и отдаёт их звуковой плате для воспроизведения. </para>
<para
>Предположим, есть три фрагмента. Сервер заполняет первый, звуковая плата начинает проигрывать его. Сервер заполняет второй. Сервер заполняет третий. Сервер завершает работу, другие программы могут начинать что-нибудь делать. </para>
<para
>Как только звуковая плата проиграет первый фрагмент, она начинает проигрывать второй, и сервер начинает заполнять первый фрагмент. И так далее. </para>
<para
>Максимальная задержка при этом равна (число фрагментов)*(размер каждого фрагмента)/((частота дискретизации)*(размер одного отсчета)). С параметрами 44 Кгц стерео и 7 фрагментов размером по 1024 байта (настройки aRts по умолчанию), мы получим 40 мс. </para>
<para
>Данное значение может быть настроено соответственно вашим нуждам. Конечно, загрузка <acronym
>CPU</acronym
> увеличивается с уменьшением задержки, так как звуковому серверу нужно заполнять буфера более часто, и в меньших размерах. Почти невозможно получить хорошие значения без предоставления звуковому серверу приоритета реального времени, иначе вы часто будете слышать прерывание звучания. </para>
<para
>Конечно, вполне реально сделать что-нибудь вроде 3-х фрагментов, каждый размером 256 байтов, которые дали бы задержку в 4.4 мс. С 4.4 мс задержки и загрузка <acronym
>CPU</acronym
> &arts; будет около 7.5%. С 40 мс задержкой она составит около 3% (на PII-350, и данное значение может зависеть от вашей звуковой карты, версии ядра и т.д.). </para>
<para
>Затем есть время, необходимое для прохода звука от колонок до ваших ушей. Предполагается что от вас до колонок 2 метра. Звук перемещается со скоростью 330 метров в секунду. Мы можем округлить данное время до 6 мс. </para>
</sect2>
<sect2 id="latency-streaming">
<title
>Задержка в потоковых программах</title>
<para
>Потоковые программы производят звук самостоятельно. Рассмотрим игру, которая создает постоянный поток отсчетов, и должна быть адаптирована для &arts;. Например, когда я нажимаю кнопку, фигура, которой я играю, прыгает, и раздаётся звук. </para>
<para
>Сначала вам нужно знать, как &arts; работает с потоковым звуком. Это очень похоже на ввод/вывод со звуковой платой. Игра посылает некоторые пакеты с отсчетами звуковому серверу. Например, три пакета. Как только звуковой сервер закончит с первым пакетом, он посылает подтверждение игре, что этот пакет обработан. </para>
<para
>Игра создаёт другой звуковой пакет и посылает его серверу. Тем временем сервер начинает поглощать второй пакет, и так далее. Задержка здесь схожа с простым случаем: </para>
<itemizedlist>
<listitem>
<para
>Время, пока ядро не оповестит сервер X11, что клавиша была нажата. </para>
</listitem>
<listitem>
<para
>Время, пока сервер X11 не оповестит игру, что кнопка была нажата. </para>
</listitem>
<listitem>
<para
>Время, пока игра не определит, что по этой кнопке стоит проиграть звук. </para>
</listitem>
<listitem>
<para
>Время, пока звуковой пакет, который игра начала передавать серверу, не достигнет звукового сервера. </para>
</listitem>
<listitem>
<para
>Время, необходимое для прохождения звука, который звуковой сервер смешивает с другим выводимым звуком в один, через буфер данных, пока тот действительно не достигнет места, где звуковая плата проигрывает звук. </para>
</listitem>
<listitem>
<para
>Время, нужное для достижения звука из колонок вашего уха. </para>
</listitem>
</itemizedlist>
<para
>Внешние задержки, как и ранее, вне рамок этого документа. </para>
<para
>Очевидно, что задержка потокового звука зависит от времени, необходимого для воспроизведения всех пакетов потока. Итак, это (число пакетов)*(размер пакета)/((частота образцов)*(размер отсчета)). </para>
<para
>Как вы видите, эта же формула применяется для фрагментов. Конечно, для игр это несущественно из-за такой же небольшой задержки, как и раньше. Я бы посоветовал использовать конфигурацию для игр 2048 байт на пакет и использование 3 пакетов. Результирующая задержка составит 35 мс. </para>
<para
>Это основано на следующей оценке. Допустим, игра идёт с частотой 25 кадров в секунду. Возможно, вы не заметите различий вывода звука в пределах одного кадра. Итак, задержка в 1/25 с при потоковом воспроизведении приемлема, что составляет 40 мс. </para>
<para
>Большинство не запускает игры с приоритетом реального времени, и опасностью перерывов в звуке нельзя пренебрегать. Потоковый звук с 3 пакетами по 256 возможен - я проверял - но сильно загружает процессор. </para>
<para
>Задержки сервера вы можете расчитать точно также. </para>
</sect2>
<sect2 id="cpu-usage">
<title
>О загрузке процессора</title>
<para
>Имеется масса факторов, которые влияют на загрузку процессора в сложных случаях, с потоковыми и прочими приложениями и модулями сервера. Назовём несколько: </para>
<itemizedlist>
<listitem>
<para
>Загрузка процессора необходимыми вычислениями. </para>
</listitem>
<listitem>
<para
>Накладные расходы внутреннего планирования &arts; - &arts; решает когда,какой модуль и что должен вычислить. </para>
</listitem>
<listitem>
<para
>Накладные расходы преобразования целых чисел в числа с плавающей запятой. </para>
</listitem>
<listitem>
<para
>Накладные расходы протокола &MCOP;. </para>
</listitem>
<listitem>
<para
>Ядро: переключение процессов/контекстов </para>
</listitem>
<listitem>
<para
>Ядро: накладные расходы связи </para>
</listitem>
</itemizedlist>
<para
>Если запущены два потока, то их загрузка процессора суммируется. Если вы применяете фильтр, то требуются некоторые вычисления. Упрощённо, сложение двух потоков может использовать четыре цикла <acronym
>CPU</acronym
> на сложение, для 350Мгц процессора это 44100*2*4/350000000 = 0.1% использования <acronym
>CPU</acronym
>. </para>
<para
>Внутреннее планирование &arts;: &arts; должен решить, какой модуль, когда и что вычисляет. Это требует времени. Вам нужен профилировщик, если вы заинтересованы в этом. Чем меньше требуется обработки в реальном времени, (например, за счет обработки больших буферов), тем меньше накладные расходы планирования. Если обрабатывать блоки по 128 отсчетов (с использованием фрагментов размером по 512 байт), то на накладные расходы планирования можно не обращать внимания. </para>
<para
>Накладные расходы преобразования целых чисел в числа с плавающей точкой: внутри &arts; используются данные в формате с плавающей точкой. Это упрощает обработку и на последних процессорах не медленнее, чем целочисленные операции. Конечно, если клиент использует целочисленные данные, (подобно игре, которая выводит звук через &arts;), то требуется их преобразовать. Также и звуковая карта принимает целочисленные данные, так что их нужно преобразовывать. </para>
<para
>Вот данные для процессора Celeron, такты процессора на обработку одного отсчета, с опцией -O2 egcs 2.91.66 (получены Евгением Смитом <email
>hamster@null.ru</email
>). Конечно, это сильно зависит от процессора: </para>
<programlisting
>convert_mono_8_float: 14
convert_stereo_i8_2float: 28
convert_mono_16le_float: 40
interpolate_mono_16le_float: 200
convert_stereo_i16le_2float: 80
convert_mono_float_16le: 80
</programlisting>
<para
>Итак загрузка составляет в среднем 1% для преобразования и 5% для интерполяции на 350 МГц процессоре. </para>
<para
>Накладные расходы &MCOP; протокола, по приблизительным подсчётам, 9000 обращений в секунду. Многое из этого не является недостатком &MCOP;, но относится к двум моментам из ядра операционной системы, описанным ниже.Это даёт основу для вычислений стоимости затрат потоковой обработки. </para>
<para
>Каждый пакет данных, передаваемый через потоки, может рассматриваться как одно &MCOP; обращение. Конечно же, большие пакеты медленнее, чем 9000 пакетов/с, но это только прикидки. </para>
<para
>Предполагается, что вы используете пакеты размером 1024 байт. Таким образом, для передачи потока с 44 КГц стерео вам необходимо передать 44100*4/1024 = 172 пакета в секунду. Предполагается, что вы можете при 100% загрузке процессора передать 9000 пакетов, тогда получите (172*100)/9000 = 2% использования <acronym
>CPU</acronym
> для потока с пакетами в 1024 байт. </para>
<para
>Это округлённые расчеты. Однако, они показывают, что намного лучше было бы, если время задержки вас устраивает, использовать к примеру пакеты по 4096 байт. Тогда мы имеем формулу вычисления размера пакета, который приводит к 100% загрузке <acronym
>CPU</acronym
>, 44100*4/9000 = 19.6 отсчета, и таким образом получаем формулу для оценок: </para>
<para
>использование <acronym
>CPU</acronym
> потоком в процентах = 1960/(ваш размер пакета) </para>
<para
>которая даёт нам 0.5% использования <acronym
>CPU</acronym
> потоками с размером пакета в 4096 байт. </para>
<para
>Переключение ядром процессов/контекстов: это часть накладных расходов &MCOP; протокола. Переключение между двумя процессами требует времени - отображение новой памяти, непопадание в кэш, или что-то ещё (если это читает эксперт по ядру - дайте мне знать, что точно является причиной). Это забирает время. </para>
<para
>Я не знаю, как с какой частотой &Linux; может переключать контексты, но это число не бесконечно. Таким образом, накладные расходы &MCOP; протокола, как я полагаю, в немалой степени вызваны переключением контекстов. В начале разработки &MCOP; я протестировал использование такого взаимодействия внутри одного процесса, и это было намного быстрее (в четыре раза быстрее или около того). </para>
<para
>Ядро: накладные расходы взаимодействия: Это часть накладных расходов протокола &MCOP;. Передача данных между процессами сейчас сделана через сокеты. Это удобно, так как для определения прихода данных могут быть использованы обычные методы select(). И это может также сочетаться с другими источниками ввода-вывода, такими как звуковой ввод-вывод, сервер X11 или любыми другими. </para>
<para
>Тем не менее, вызовы чтения и записи стоят процессорных тактов. Для небольших обращений (таких как передача одного midi события) это и не так плохо, но для больших обращений (как передача одного видеокадра размером в несколько мегабайт) это уже точно проблема. </para>
<para
>Использование общей разделяемой памяти для &MCOP; там, где это возможно, было бы лучшим решением. Однако это должно быть сделано прозрачно для программиста приложения. </para>
<para
>Возьмите профилировщик или сделайте другие тесты для выяснения, в какой степени на обработку текущего аудио потока влияет неиспользование общей памяти. Совсем не плохо, когда обработка аудио потока (проигрывание mp3) с помощью &artsd; и <application
>artscat</application
> с загружает процессор на 6% (на 5% с помощью только mp3-декодера). Конечно, это включает всё от выполнения необходимых вычислений до издержек работы с сокетами, таким образом я бы сказал, что возможно сэкономить 1%, используя общую память. </para>
</sect2>
<sect2 id="hard-numbers">
<title
>Некоторые сложные случаи</title>
<para
>Это сделано в текущем разрабатываемом варианте. Я также хотел проверить по-настоящему сложные случаи, и это не то, что должны использовать обычные приложения. </para>
<para
>Я написал приложение, названное streamsound, которое посылает потоковые данные &arts;. Оно работает с приоритетом реального времени (без проблем), и с одним небольшим модулем сервера (масштабирование и ограничение громкости): </para>
<programlisting
>4974 stefan 20 0 2360 2360 1784 S 0 17.7 1.8 0:21 artsd
5016 stefan 20 0 2208 2208 1684 S 0 7.2 1.7 0:02 streamsound
5002 stefan 20 0 2208 2208 1684 S 0 6.8 1.7 0:07 streamsound
4997 stefan 20 0 2208 2208 1684 S 0 6.6 1.7 0:07 streamsound
</programlisting>
<para
>Каждый из этих процессов - обработка потока с 3 фрагментами по 1024 байта (18мс). Имеется три таких клиента, работающих одновременно. Я знаю, что это выглядит немного излишне, но как я сказал: возьмите профилировщик и выясните, на что тратится время, и если вы сочтёте необходимым, то исправьте. </para>
<para
>Конечно, я не думаю, что в реальной жизни понадобится такая потоковая обработка. Для тестов я пробовал то, что уменьшаеит задержку до минимума. Результат: вы можете обрабатывать потоки без прерываний с одним клиентским приложением, если вы взяли 2 фрагмента по 128 байт между aRts и звуковой картой, и между клиентским приложением и aRts. Это означает, что вы имеете общую максимальную задержку в 128*4/44100*4 = 3 мс, где 1.5 мс происходят из-за ввода/вывода звуковой карты и 1.5 мс из-за взаимодействия с &arts;. Оба приложения нужно запускать с приоритетом реального времени. </para>
<para
>Конечно, это сильно загружает процессор. Данный пример загружает мой P-II/350 приблизительно на 45%. Также будут слышны щелчки, если вы запустите top, начнёте перемещать окна на вашем дисплее X11 или выполнять операции дискового ввода/вывода. Всё это - проблемы ядра. Проблема планирования двух или большего числа приложений с приоритетом реального времени очень сложна, и ещё сложнее, если они взаимодействуют, оповещая друг друга и т. д. </para>
<para
>Наконец, более жизненный пример. Это &arts; с artsd и одним artscat (один клиент обработки потока) запустивший 16 фрагментов по 4096 байт: </para>
<programlisting
>5548 stefan 12 0 2364 2364 1752 R 0 4.9 1.8 0:03 artsd
5554 stefan 3 0 752 752 572 R 0 0.7 0.5 0:00 top
5550 stefan 2 0 2280 2280 1696 S 0 0.5 1.7 0:00 artscat
</programlisting>
</sect2>
</sect1>
<!-- TODO
<sect1 id="dynamic-instantiation">
<title
>Dynamic Instantiation</title>
</sect1>
-->
<sect1 id="busses">
<title
>Шины</title>
<para
>Шины - это динамически созданные соединения, которые передают аудио. В основном, это некоторые входные и выходные связи. Все сигналы из входных связей складываются и посылаются на выходные связи. </para>
<para
>Шины сейчас реализованы для операций со стерео, итак вы можете передавать через шины только стерео данные. Если вы хотите моно, хорошо, передавайте их только по одному каналу и установите другой в ноль или что-нибудь иное. Что же вам нужно сделать для этого, создать один или несколько Synth_BUS_UPLINK объектов и указать им имя шины, по которой они должны общаться ( например <quote
>аудио</quote
> или <quote
>барабаны</quote
>. Просто направляйте данные туда. </para>
<para
>Затем, вам нужно создать один или несколько Synth_BUS_DOWNLINK объектов выходных связей, и указать им имя шины (<quote
>аудио</quote
> или <quote
>барабаны</quote
> ... если это подходит для передачи данных через них), и обработанные данные будут на выходе. </para>
<para
>Входные и выходные связи могут находиться внутри различных структур, вы можете даже иметь несколько запущенных копий &arts-builder; и начать передавать в одной и принимать данные в другой. </para>
<para
>Шины хороши тем, что они полностью динамические. Клиенты могут подключаться и отключаться во время работы, не будет прерываний потока или шума. </para>
<para
>Конечно, вы не должны отключать клиента, проигрывающего сигнал, после отключения от шины возможно сигнал не будет нулевым, и тогда поток прервётся. </para>
</sect1>
<!-- TODO
<sect1 id="network-ransparency">
<title
>Network Transparency</title>
</sect1>
<sect1 id="security">
<title
>Security</title>
</sect1>
<sect1 id="effects">
<title
>Effects and Effect Stacks</title>
</sect1>
-->
<sect1 id="trader">
<title
>Трейдинг</title>
<para
>&arts;/&MCOP; основан на распределении задач по небольшим компонентам. Благодаря этому система становится более гибкой, тогда можно расширять систему, просто добавляя новые компоненты, реализующие новые эффекты, форматы файлов, генераторы, элементы пользовательского интерфейса, ... Поскольку почти все элементы - это компоненты, всё можно расширять очень просто, без изменения существующих исходников. Новые компоненты могут быть просто динамически загружены для улучшения уже существующих приложений. </para>
<para
>Для этого необходимы две вещи: </para>
<itemizedlist>
<listitem>
<para
>Компоненты должны извещать о себе сами - они должны описывать собственную функциональность так, чтобы другие приложения могли бы использовать их. </para>
</listitem>
<listitem>
<para
>Приложения должны активно просматривать наличие компонентов, которые они могут использовать, вместо использования одних и тех же способов выполнения задач. </para>
</listitem>
</itemizedlist>
<para
>Обобщенно: компоненты, которые говорят: <quote
>здесь я, я то что нужно, используй меня</quote
>, и приложения (или если вам нужно, другие компоненты), которые выходят и смотрят, какой компонент они могут использовать для выполнения задачи. Это называется трейдинг. </para>
<para
>В &arts;, компоненты описывают сами себя, указывая значения, которые они <quote
>поддерживают</quote
> как свойства. Типичным свойством для компонента загрузки файлов может быть расширение обрабатываемых файлов. Типичные значения могут быть <literal role="extension"
>wav</literal
>, <literal role="extension"
>aiff</literal
> или <literal role="extension"
>mp3</literal
>. </para>
<para
>Фактически каждый компонент может предлагать несколько различных значений для одного свойства. Так, один компонент может предлагать прочитать оба, <literal role="extension"
>wav</literal
> и <literal role="extension"
>aiff</literal
> файла, указав что поддерживает эти значения для свойства <quote
>Расширение</quote
>. </para>
<para
>Для этого компонент должен поместить в соответствующее место файл <literal role="extension"
>.mcopclass</literal
>, содержащий поддерживаемые свойства. Для нашего примера это может выглядеть подобно так (и установлено в <filename
><replaceable
>componentdir</replaceable
>/Arts/WavPlayObject.mcopclass</filename
>): </para>
<programlisting
>Interface=Arts::WavPlayObject,Arts::PlayObject,Arts::SynthModule,Arts::Object
Author="Stefan Westerfeld <stefan@space.twc.de>"
URL="http://www.arts-project.org"
Extension=wav,aiff
MimeType=audio/x-wav,audio/x-aiff
</programlisting>
<para
>Важно, чтобы имя <literal role="extension"
>.mcopclass</literal
>-файла также было содержательным. Трейдер не просматривает содержание всего файла, если файл (как здесь) назван <filename
>Arts/WavPlayObject.mcopclass</filename
>, а интерфейс компонента назван <interfacename
>Arts::WavPlayObject</interfacename
> (модули отображаются на каталоги). </para>
<para
>Для просмотра компонентов имеется два интерфейса (которые определены в <filename
>core.idl</filename
>, так что вы имеете их в каждом приложении), названные <interfacename
>Arts::TraderQuery</interfacename
> и <interfacename
>Arts::TraderOffer</interfacename
>. Вы ищете нужные компоненты следующим образом: </para>
<orderedlist>
<listitem>
<para
>Создаёте объект запроса: </para>
<programlisting
>Arts::TraderQuery запрос;
</programlisting>
</listitem>
<listitem>
<para
>Укажите, что вам нужно. Как вы видели выше, компоненты описывают сами себя, используя свойства, для которых они устанавливают определённые значения. Так что можно указать, что вам нужно, выбрав компоненты, которые поддерживают определённые значения для свойств. Это делается с помощью методов TraderQuery: </para>
<programlisting
>query.supports("Interface","Arts::PlayObject");
query.supports("Extension","wav");
</programlisting>
</listitem>
<listitem>
<para
>Наконец, делаем запрос, используя метод запроса. Затем (как надеемся) получаем некоторые предложения: </para>
<programlisting
>vector<Arts::TraderOffer> *offers = query.query();
</programlisting>
</listitem>
<listitem>
<para
>Сейчас вы можете проверить то, что нашли. Важен метод interfaceName интерфейса TraderOffer, который вернёт вам имя компонента, подходящего запросу. Вы также можете узнать дополнительные свойства, используя getProperty. Следующий код просто проходит по всем компонентам, выводит их имена интерфейсов (которые могли быть использованы для создания) и удаляет результаты запроса: </para>
<programlisting
>vector<Arts::TraderOffer>::iterator i;
for(i = offers->begin(); i != offers->end(); i++)
cout << i->interfaceName() << endl;
delete offers;
</programlisting>
</listitem>
</orderedlist>
<para
>Чтобы служба трейдинга была полезна, важно прийти к согласию, каковы могут быть свойства компонентов. Существенно, чтобы практически все компоненты в определённой области использовали одинаковый набор свойств для описания самих себя (и по возможности одинаковый набор значений), так что приложения или другие компоненты могли бы найти их. </para>
<para
>Author (строковый тип, необязательный): Это может быть использовано для того, чтобы показать миру, что вы это написали. Вы можете написать здесь всё, что угодно, конечно желателен адрес электронной почты. </para>
<para
>Buildable (булевый тип, рекомендуется): говорит о возможности использования компонента инструментами <acronym
>RAD</acronym
> (такими как &arts-builder;), которые используют компоненты, устанавливая соответствие свойствам и соединяя порты. Рекомендуется устанавливать данное значение в истинное для большинства компонент обработки сигналов (таких как фильтры, эффекты, осцилляторы и т. д.), и для всех других, которые могут быть использованы в <acronym
>RAD</acronym
>, но не для внутренней работы как, например, <interfacename
>Arts::InterfaceRepo</interfacename
>. </para>
<para
>Extension (строковый тип, используется, когда это уместно): вcё, что связано с файлами, должно задавать это. Вы можете указать расширение файла в нижнем регистре без <quote
>.</quote
>, поэтому что-то типа <userinput
>wav</userinput
> подходит. </para>
<para
>Interface (строковый тип, необходим): должно включать полный список поддерживаемых (полезных) интерфейсов вашими компонентами, возможно включая <interfacename
>Arts::Object</interfacename
> и, если это возможно, <interfacename
>Arts::SynthModule</interfacename
>. </para>
<para
>Language (строковый тип, рекомендуется): если вы хотите загружать компонент динамически, то нужно указать здесь язык написания компонента. Сейчас допускается только значение <userinput
>C++</userinput
>, которое означает, что компонент был написан с использованием нормального <acronym
>API</acronym
> C++. Если вы сделали так, то вам ещё нужно установить свойство <quote
>Library</quote
>, описанное ниже. </para>
<para
>Library (строковый тип, используется, когда это уместно): компоненты, написанные на C++, могут быть динамически загружены. Для этого вам нужно скомпилировать их в динамически загружаемые <literal role="extension"
>.la</literal
> модули, используя libtool. Здесь вы можете указать имя <literal role="extension"
>.la</literal
>-файла, который содержит ваш компонент. Не забудьте использовать REGISTER_IMPLEMENTATION (всегда). </para>
<para
>MimeType (строковый тип, используется, когда это уместно): вcё, что связано с файлами, должно задавать это. Здесь вы можете установить стандартный mimetype в нижнем регистре, для примера <userinput
>audio/x-wav</userinput
>. </para>
<para
>&URL; (строковый тип, используется по усмотрению автора): если вы хотите указать людям, где они могут найти последнюю версию компонента (или домашнюю страницу или что-нибудь другое), вы можете сделать это. Это может быть стандартным адресом &HTTP; или &FTP;. </para>
</sect1>
<!-- TODO
<sect1 id="midi-synthesis">
<title
><acronym
>MIDI</acronym
> Synthesis</title>
</sect1>
<sect1 id="instruments">
<title
>Instruments</title>
</sect1>
<sect1 id="session-management">
<title
>Session Management</title>
</sect1>
<sect1 id="full-duplex">
<title
>Full duplex Audio</title>
</sect1>
-->
<sect1 id="namespaces">
<title
>Пространства имен в &arts;</title>
<sect2 id="namespaces-intro">
<title
>Введение</title>
<para
>Каждое пространство имен соответствует <quote
>модулю</quote
>, объявленному в &IDL; &MCOP;. </para>
<programlisting
>// mcop idl
module M {
interface A
{
}
};
interface B;
</programlisting>
<para
>В этом случае, созданный С++ код для &IDL; выглядел бы так: </para>
<programlisting
>// C++ header
namespace M {
/* объявление A_base/A_skel/A_stub и подобных */
class A { // описание класса
/* [...] */
};
}
/* объявление B_base/B_skel/B_stub и подобных */
class B {
/* [...] */
};
</programlisting>
<para
>Итак, когда вы ссылаетесь на классы из примера выше в вашем С++ коде, вы должны написать <classname
>M::A</classname
>, но просто B. Конечно, вы можете использовать <quote
>using M</quote
> где-нибудь - как с любыми другими пространствами имён в С++. </para>
</sect2>
<sect2 id="namespaces-how">
<title
>Как &arts; использует пространства имён</title>
<para
>Есть одно глобальное пространство имён, названное <quote
>Arts</quote
>, которое используют все программы и библиотеки, относящиеся к &arts;, для объявления своих интерфейсов. Это означает, что когда пишется С++ код, который зависит от &arts;, вы обычно должны добавлять префикс <classname
>Arts::</classname
> для каждого используемого класса, как в данном примере: </para>
<programlisting
>int main(int argc, char **argv)
{
Arts::Dispatcher dispatcher;
Arts::SimpleSoundServer server(Arts::Reference("global:Arts_SimpleSoundServer"));
server.play("/var/foo/somefile.wav");
</programlisting>
<para
>Другая альтернатива - описать использование пространства имён <quote
>Arts</quote
> только один раз, подобно: </para>
<programlisting
>using namespace Arts;
int main(int argc, char **argv)
{
Dispatcher dispatcher;
SimpleSoundServer server(Reference("global:Arts_SimpleSoundServer"));
server.play("/var/foo/somefile.wav");
[...]
</programlisting>
<para
>В &IDL; файлах у вас нет выбора. Если вы пишете код, который относится к &arts;, то вы должны заключить его в конструкцию module &arts;. </para>
<programlisting
>// IDL файл для aRts:
#include <artsflow.idl>
module Arts { // поместить это в пространство имён Arts
interface Synth_TWEAK : SynthModule
{
in audio stream invalue;
out audio stream outvalue;
attribute float tweakFactor;
};
};
</programlisting>
<para
>Если вы пишете код, который не относится к &arts;, то не обязаны помещать его в пространство имён <quote
>Arts</quote
>. Конечно, вы можете сделать собственное пространство имён, если вам это нужно. В любом случае, вы должны устанавливать соответствующий префикс для классов из &arts;. </para>
<programlisting
>// IDL файл для кода, не относящегося к &arts;:
#include <artsflow.idl>
// написав без объявления модуля указываем, что созданные классы не используют
// пространства имён:
interface Synth_TWEAK2 : Arts::SynthModule
{
in audio stream invalue;
out audio stream outvalue;
attribute float tweakFactor;
};
// конечно, вы можете ещё выбрать собственное пространство имён, если вам нужно,
// так что, если вы пишете приложение "PowerRadio", вы могли, например, сделать так:
module PowerRadio {
struct Station {
string name;
float frequency;
};
interface Tuner : Arts::SynthModule {
attribute Station station; // нет необходимости в префиксе Station, так как модуль тот же
out audio stream left, right;
};
};
</programlisting>
</sect2>
<sect2 id="namespaces-implementation">
<title
>Подробности: как работает реализация класса</title>
<para
>Часто, в интерфейсах, преобразованиях, методах и пр. &MCOP; нуждается в описании имён типов или интерфейсов. Они представляются в виде строк в общей &MCOP; структуре данных, в то время как пространство имён написано на С++. Это означает, что строки содержат <quote
>M::A</quote
> и <quote
>B</quote
>, из примера выше. </para>
<para
>Заметим, что это применяется, даже если внутри текста &IDL; не было квалификаторов пространства имён, поскольку из контекста ясно, в каком пространстве имён используется интерфейс <interfacename
>A</interfacename
>. </para>
</sect2>
</sect1>
<sect1 id="threads">
<title
>Многопоточность в &arts;</title>
<sect2 id="threads-basics">
<title
>Основы</title>
<para
>Многопоточность доступна не на всех платформах. Вот почему &arts; был изначально написан без использования потоков. Для всех задач может быть написано как многопоточное, так и однопоточное решение, которое делает то же самое. </para>
<para
>Так, вместо аудиовывода в отдельном блокирующем потоке &arts; использует аудиовывод без блокировок, и узнаёт, когда нужно записывать следующий фрагмент данных с помощью функции <function
>select()</function
>. </para>
<para
>Конечно, &arts; (в последних версиях) предоставляет поддержку для использования в объектах многопоточности. К примеру, если вы уже имеете код для проигрывателя <literal role="extension"
>mp3</literal
>, и код предполагает запуск декодера <literal role="extension"
>mp3</literal
> в отдельном потоке, то это проще всего сделать с использованием многопоточности. </para>
<para
>Реализация &arts;/&MCOP; опирается на разделение состояний между различными объектами в явном и неявном виде. Список объектов в разделяемой области включает в себя: </para>
<itemizedlist>
<listitem
><para
>Объект диспетчера, который осуществляет взаимодействие &MCOP;. </para>
</listitem>
<listitem>
<para
>Подсчёт ссылок, связей (Smartwrappers). </para>
</listitem>
<listitem>
<para
>Диспетчер ввода-вывода, который реализует таймер и следит за дескрипторами файлов. </para>
</listitem>
<listitem>
<para
>Менеджер объектов, который создаёт объекты и динамично загружает модули. </para>
</listitem>
<listitem>
<para
>Объект контроля потока, который вызывает calculateBlock в подходящих ситуациях. </para>
</listitem>
</itemizedlist>
<para
>Ни один из предыдущих объектов не предполагает одновременного использования (например, вызова из различных потоков одновременно). В общем, имеются два подхода к разрешению этой проблемы: </para>
<itemizedlist>
<listitem>
<para
>для вызова функций этих объектов требуется создать блокировку перед использованием. </para>
</listitem>
<listitem>
<para
>написание кода этих объектов действительно безопасным с точки зрения многопоточности и/или создание для каждой потока своей копии. </para>
</listitem>
</itemizedlist>
<para
>&arts; следует первому пути: вам нужна блокировка всякий раз, когда вы используете любой из этих объектов. Второй путь сложнее. Попытка реализовать его доступна на момент написания по ссылке <ulink url="http://space.twc.de/~stefan/kde/download/arts-mt.tar.gz"
> http://space.twc.de/~stefan/kde/download/arts-mt.tar.gz</ulink
>. На настоящий момент простой подход, возможно, работает лучше и создаёт меньше проблем с существующими приложениями. </para>
</sect2>
<sect2 id="threads-locking">
<title
>Когда и как задавать блокировку?</title>
<para
>Можно установить или освободить блокировку двумя функциями: </para>
<itemizedlist>
<listitem>
<para>
<ulink
url="http://space.twc.de/~stefan/kde/arts-mcop-doc/arts-reference/headers/Arts__Dispatcher.html#lock"
><function
>Arts::Dispatcher::lock()</function
></ulink>
</para>
</listitem>
<listitem>
<para>
<ulink
url="http://space.twc.de/~stefan/kde/arts-mcop-doc/arts-reference/headers/Arts__Dispatcher.html#unlock"
><function
>Arts::Dispatcher::unlock()</function
></ulink>
</para>
</listitem>
</itemizedlist>
<para
>Обычно вам не нужна явная блокировка (и вы не должны пробовать делать это), если это уже сделано. Список условий, когда блокировка уже есть: </para>
<itemizedlist>
<listitem>
<para
>Вы получаете обратный вызов из диспетчера ввода-вывода (по таймеру или через дескриптор файлов). </para>
</listitem>
<listitem>
<para
>Вы получаете вызов какого-то &MCOP; запроса. </para>
</listitem>
<listitem>
<para
>Вы получаете вызов менеджера оповещения. </para>
</listitem>
<listitem>
<para
>Вы получаете вызов системы контроля потока (calculateBlock) </para>
</listitem>
</itemizedlist>
<para
>Имеются также некоторые функции, которые вы можете вызывать только в основном потоке, и для них вам никогда не нужно получать блокировку: </para>
<itemizedlist>
<listitem>
<para
>Конструктор/деструктор диспетчера объектов или ввода-вывода. </para>
</listitem>
<listitem>
<para
><methodname
>Dispatcher::run()</methodname
> / <methodname
>IOManager::run()</methodname
> </para>
</listitem>
<listitem>
<para
><methodname
>IOManager::processOneEvent()</methodname
></para>
</listitem>
</itemizedlist>
<para
>И всё. Для всего остального, что хоть как-то взаимодействует с &arts;, вам нужно получить блокировку и освободить её сразу после завершения. Всегда. Вот простой пример: </para>
<programlisting
>class SuspendTimeThread : Arts::Thread {
public:
void run() {
/*
* Вам нужна здесь блокировка, потому что:
* - создание ссылки нуждается в блокировке (глобальной: управление передаётся
* менеджеру объектов, который может нуждаться в объекте GlobalComm
* для поиска, куда нужно соединиться)
* - подключение smartwrapper нуждается в блокировке
* - создание объекта из ссылки нуждается в блокировке (потому что это
* может требоваться для подключения к серверу)
*/
Arts::Dispatcher::lock();
Arts::SoundServer server = Arts::Reference("global:Arts_SoundServer");
Arts::Dispatcher::unlock();
for(;;) { /*
* здесь вам нужна блокировка, так как
* - отключение smartwrapper нуждается в блокировке (может
* потребоваться создать что-то)
* - оповещения MCOP нуждаются в блокировке
*/
Arts::Dispatcher::lock();
long seconds = server.secondsUntilSuspend();
Arts::Dispatcher::unlock();
printf("seconds until suspend = %d",seconds);
sleep(1);
}
}
}
</programlisting>
</sect2>
<sect2 id="threads-classes">
<title
>Классы для работы с многопоточностью</title>
<para
>Доступны следующие классы: </para>
<itemizedlist>
<listitem>
<para
><ulink url="http://www.arts-project.org/doc/headers/Arts__Thread.html"
><classname
> Arts::Thread</classname
></ulink
> - интерфейс к потокам. </para>
</listitem>
<listitem>
<para
><ulink url="http://www.arts-project.org/doc/headers/Arts__Mutex.html"
> <classname
>Arts::Mutex</classname
></ulink
> - интерфейс к мьютексам. </para>
</listitem>
<listitem>
<para
><ulink url="http://www.arts-project.org/doc/headers/Arts__ThreadCondition.html"
> <classname
>Arts::ThreadCondition</classname
></ulink
> - интерфейс к возобновлению условно приостановленных потоков. </para>
</listitem>
<listitem>
<para
><ulink url="http://www.arts-project.org/doc/headers/Arts__SystemThreads.html"
><classname
>Arts::SystemThreads</classname
></ulink
> - интерфейс к потокам самой операционной системы (несколько полезных функций для прикладных программистов). </para>
</listitem>
</itemizedlist>
<para
>См. ссылки на документацию. </para>
</sect2>
</sect1>
<sect1 id="references-errors">
<title
>Ссылки и обработка ошибок</title>
<para
>Ссылки &MCOP; - одна из основных концепций в программировании &MCOP;. В этой части описано, как используются ссылки, и даётся попытка осветить случаи отказов (сбоев работы сервера). </para>
<sect2 id="references-properties">
<title
>Основные свойства ссылок</title>
<itemizedlist>
<listitem>
<para
>Ссылка &MCOP; не является объектом, но ссылается на объект: даже если следующее объявление, <programlisting>
Arts::Synth_PLAY p;
</programlisting
> выглядит похожим на определение объекта, оно только объявляет ссылку на объект. Как С++ программист, вы можете воспринимать это как Synth_PLAY *, указатель на объект Synth_PLAY. Главным образом это означает, что p подобно указателю на NULL. </para>
</listitem>
<listitem>
<para
>Вы можете создать ссылку на NULL явно. </para>
<programlisting
>Arts::Synth_PLAY p = Arts::Synth_PLAY::null();
</programlisting>
</listitem>
<listitem>
<para
>Вызов функций с использованием ссылки на NULL приводит к сбою в core </para>
<programlisting
>Arts::Synth_PLAY p = Arts::Synth_PLAY::null();
string s = p.toString();
</programlisting>
<para
>приводит к сбою. Сравним с указателями, по существу это похоже на <programlisting>
QWindow* w = 0;
w->show();
</programlisting
>. Каждый программист С++ знает, как избегать таких ситуаций. </para>
</listitem>
<listitem>
<para
>Неинициализированные объекты кое-как пытаются создать себя сами при первом использовании </para>
<programlisting
>Arts::Synth_PLAY p;
string s = p.toString();
</programlisting>
<para
>это несколько отличается от разыменования указателя на NULL. Вы вообще не указали объекту, чем он является, и пытаетесь использовать его. Вообразим здесь, что вы хотели иметь новую локальную копию объекта Arts::Synth_PLAY. Конечно вы могли хотеть что-то ещё (вроде создания объекта где-то ещё или использования существующего внешнего объекта. Так или иначе, объект будет как-то создан, но созданный подобным образом объект не будет работать до тех пор пока вы не присвоите ему какое-то значение (также как и нулевая ссылка). </para>
<para
>Эквивалент в терминах С++<programlisting>
QWidget* w;
w->show();
</programlisting
> что в C++ безусловно приводит к ошибке обращения к памяти. Итак, есть отличия. Такое создание объекта может быть ошибочным потому, что необязательно существует реализация вашего интерфейса. </para>
<para
>Для примера, рассмотрим абстрактную конструкцию, подобную Arts::PlayObject. Имеются вполне определённые объекты для воспроизведения mp3 или wav, но <programlisting>
Arts::PlayObject po;
po.play();
</programlisting
> точно не будет работать. Проблема в том, что хотя код пытается как-то создать объект PlayObject, это не работает, так как имеются только реализации, подобные Arts::WavPlayObject. Так что создавать объекты вы можете только тогда, когда существует реализация. </para>
</listitem>
<listitem>
<para
>Ссылки могут указывать на один и тот же объект </para>
<programlisting
>Arts::SimpleSoundServer s = Arts::Reference("global:Arts_SimpleSoundServer");
Arts::SimpleSoundServer s2 = s;
</programlisting>
<para
>создаст две ссылки на один и тот же объект. Это не копирование значений и не создание двух объектов. </para>
</listitem>
<listitem>
<para
>Все ссылки на объекты подсчитываются. Так что объект, на который нет ссылок, удаляется. Нет специальных методов для удаления объектов, однако, вы можете использовать что-то похожее на это <programlisting>
Arts::Synth_PLAY p;
p.start();
[...]
p = Arts::Synth_PLAY::null();
</programlisting
> для удаления объекта Synth_PLAY в конце. В особенности, никогда не требуется использовать new и delete вместе со ссылками. </para>
</listitem>
</itemizedlist>
</sect2>
<sect2 id="references-failure">
<title
>Случаи сбоев</title>
<para
>Ссылки могут указывать на внешние объекты, а серверы, содержащие такие объекты, могут упасть. Что тогда происходит? </para>
<itemizedlist>
<listitem>
<para
>Сбой сервера не изменяет ссылку, если это нулевая ссылка. Это означает, что если <function
>foo.isNull()</function
> была <returnvalue
>true</returnvalue
> до падения сервера, тогда она также вернёт <returnvalue
>true</returnvalue
> и после падения сервера. Это также означает, что, если <function
>foo.isNull()</function
> была <returnvalue
>false</returnvalue
> до падения сервера (ссылка foo ссылалась на объект), тогда она также вернёт <returnvalue
>false</returnvalue
> и после падения сервера. </para>
</listitem>
<listitem>
<para
>Вызовы методов действительной ссылки безопасны. Предположим, что сервер содержащий объект calc, упал. Подобный вызов <programlisting>
int k = calc.subtract(i,j)
</programlisting
>безопасен. Явно subtract вернёт что-то неверное, потому что внешний объект уже не существует. В этом случае (k == 0) может быть верным. В основном, операции возвращают в качестве результата что-нибудь <quote
>нейтральное</quote
>, как 0.0, нулевые ссылки на объекты или пустые строки, когда объект более не существует. </para>
</listitem>
<listitem>
<para
>Существует функция <function
>error()</function
> для проверки того, как что-то отработало. </para>
<para
>В предыдущем случае, <programlisting>
int k = calc.subtract(i,j)
if(k.error()) {
printf("k is not i-j!\n");
}
</programlisting
> может вывести <computeroutput
>k is not i-j</computeroutput
>, если внешний вызов не работает. Иначе <varname
>k</varname
> будет действительным результатом выполнения операции subtract, выполненной внешним объектом (сервер не разрушен). Однако для методов, выполняющих операции подобные удалению файлов, вы не можете знать, что произошло в действительности. Конечно, это произошло, если <function
>.error()</function
> вернула <returnvalue
>false</returnvalue
>. Однако, если <function
>.error()</function
> вернула <returnvalue
>true</returnvalue
>, существуют две возможности: </para>
<itemizedlist>
<listitem>
<para
>Файл удалён и сервер упал сразу после удаления, но до передачи результата. </para>
</listitem>
<listitem>
<para
>Сервер упал до удаления файла. </para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para
>Использование вложенных вызовов опасно в отказоустойчивых программах </para>
<para
>Использование кода <programlisting>
window.titlebar().setTitle("foo");
</programlisting
> - плохая идея. Предположим, вы знаете, что ссылка на Window действительна. Предположим, вы вы знаете, что функция <function
>window.titlebar()</function
> возвратит ссылку на Titlebar, так как объект Window реализован верно. Однако этот код остаётся небезопасным. </para>
<para
>Что может случится, когда сервер, содержащий объект Window, падает? Невзирая на то, как хорошо реализован объект Window, операция window.titlebar() вернёт вам нулевую ссылку. И затем вызов setTitle приводит к сбою. </para>
<para
>Итак, безопасный вариант может быть таким: <programlisting>
Titlebar titlebar = window.titlebar();
if(!window.error())
titlebar.setTitle("foo");
</programlisting
>. Добавьте подходящую обработку ошибок, если вам нужно. Если вы не доверяете реализации Window, используйте код <programlisting>
Titlebar titlebar = window.titlebar();
if(!titlebar.isNull())
titlebar.setTitle("foo");
</programlisting
>, который безопаснее. </para>
</listitem>
</itemizedlist>
<para
>Имеются другие условия для сбоев, такие как нарушение сетевого соединения (предположим вы удалили кабель между сервером и клиентом, пока ваше приложение работает). Однако, такой эффект подобен сбою сервера. </para>
<para
>В целом, это ваш подход к тому, насколько строго вы пробуете отследить ошибки взаимодействия внутри вашего приложения. Можно следовать методу <quote
>если сервер падает, нужно отлаживать сервер, пока он совсем не перестанет падать</quote
>, который означает, что вам не нужно беспокоиться обо всех этих проблемах. </para>
</sect2>
<sect2 id="references-internals">
<title
>Подробности: Распределённый подсчёт ссылок</title>
<para
>Объект может существовать только, когда им владеет кто-то. Если это не так, то он немедленно прекращает существовать. Владелец указывается с помощью счётчика ссылок на объект, который увеличивается при вызове функции <function
>_copy()</function
> и уменьшается при вызове функции <function
>_release()</function
>. Как только счётчик ссылок установится в ноль, объект удаляется. </para>
<para
>Как вариант, удалённое (внешнее) использование устанавливается функцией <function
>_useRemote()</function
>, и аннулируется функцией <function
>_releaseRemote()</function
>. Эти функции ведут список серверов, из вызвавших (и поэтому владеющих объектами). Это используется в случае отключения сервера (например при его падении или сбое сети), для удаления оставшихся ссылок на объекты, с использованием функции <function
>_disconnectRemote()</function
>. </para>
<para
>Снова проблема. Рассмотрим возвращаемое значение. Обычно возвращаемое значение-объект не принадлежит вызывающей функцией. Оно, однако, также не принадлежит вызывающему объекту, до тех пор пока сообщение, содержащее объект, не будет получено. Итак, какое-то время объекты остаются <quote
>беспризорными</quote
>. </para>
<para
>Теперь, когда посылается объект, можно быть уверенным, что как только он будет получен, то его владельцем будет кто-нибудь снова, если только принимающий объект сам не успел уничтожиться. Тем не менее, это означает, что объект нужно поддерживать особо по крайней мере в течение его отправки, а возможно также и в течение приёма, чтобы он - объект - не исчез мгновенно. </para>
<para
>&MCOP; делает это с помощью <quote
>меток</quote
> объектов, которые находятся в процессе копирования. До начала копирования, вызывается функция <function
>_copyRemote</function
>. Это защищает объект от освобождения в течение 5 секунд. Когда принимающая сторона вызывает <function
>_useRemote()</function
>, метка снова сбрасывается. Итак все объекты, которые пересылаются, помечаются перед отправкой по сети. </para>
<para
>Если принимающая сторона принимает объект, который находится на том же сервере, то для этого, конечно же, не используется функция <function
>_useRemote()</function
>. Для специального случая существует функция <function
>_cancelCopyRemote()</function
> для удаления метки вручную. Имеются также способы удаления метки, основанные на таймере, но принимающая сторона в действительности может не получить объект (из-за падения сервера, сбоя сети). Тогда удаление меток выполняется с помощью специального класса <classname
>ReferenceClean</classname
>. </para>
</sect2>
</sect1>
<sect1 id="detail-gui-elements">
<title
>Элементы графического пользовательского интерфейса</title>
<para
>Элементы графического пользовательского интерфейса сейчас в стадии разработки. Эта глава описывает то, что будет воплощено, так что если вы разработчик, то сможете понять как &arts; будет работать с графическим пользовательским интерфейсом в будущем. Уже есть некоторый готовый код. </para>
<para
>Элементы графического интерфейса могут быть использованы для создания структур, взаимодействующих с пользователем. В простейшем случае пользователь мог бы непосредственно изменять некоторые параметры структуры (такие как уровень усиления, который используется в конце, перед воспроизводящим модулем). </para>
<para
>В более сложных настройках пользователь мог бы измененять параметры группы структур и/или ещё не работающих структур, таких как изменение группы <acronym
>ADSR</acronym
> активного инструмента &MIDI;, или установка имени файла для некоторых инструментов. </para
>
<para
>Также пользователю мог бы понадобиться монитор состояния синтезатора. Может представить себе осциллограф, спектроанализатор, измеритель уровня громкости и другие устройства для экспериментирования, которые рисовали бы кривую передаточной функции выбранного фильтра. </para>
<para
>Наконец, элементы интерфейса должны уметь управлять всеми структурами, того что запущено внутри &arts;. Пользователь должны уметь устанавливать инструменты для каналов midi, запуская новые эффекты, конфигурируя основной микшер, который встроен в структуру &arts;, для усиления какого-либо канала и использования другой стратегии для эквалайзеров. </para>
<para
>Вы видите - элементы пользовательского графического интерфейса, должны воспроизвести все возможности виртуальной студии &arts; для пользователя. Конечно, они также должны элегантно взаимодействовать со входами midi (бегунки должны перемещаться, если они управляют входами &MIDI;, которые также изменяются как параметры), и возможно создавать события, предоставлять пользователю возможность записывать звук на секвенсер. </para>
<para
>Если говорить техническим языком, речь идёт о базовом классе на &IDL; для всех графических элементов (<classname
>Arts::Widget</classname
>), и наследовании из него (например <classname
>Arts::Poti</classname
>, <classname
>Arts::Panel</classname
>, <classname
>Arts::Window</classname
>, ...). </para>
<para
>Тогда можно было бы реализовать эти графические примитивы, используя инструментарий, к примеру &Qt; или Gtk. Наконец, эффекты должны строить свой интерфейс на базе существующих элементов. Скажем, эффект freeverb может построить свой интерфейс с помощью пяти <classname
>Arts::Poti</classname
> и одного <classname
>Arts::Window</classname
>. Итак, если есть реализация таких окон на &Qt;, то эффекты могут отображать себя, используя &Qt;. Если имеется реализация на Gtk, то также можно работать с Gtk (что должно более или менее выглядеть и работать схожим образом). </para>
<para
>Наконец, поскольку мы здесь используем &IDL;, &arts-builder; (или другой инструмент) может визуально встраивать элементы интерфейса или автоматически создавать их из подсказок для параметров, основываясь только на интерфейсах. Совсем несложно написать класс <quote
>создания элементов интерфейса по их описанию</quote
>, который использует описание &GUI; и создаёт живой объект элемент интерфейса. </para>
<para
>Опираясь на &IDL; и компонентную модель &arts;/&MCOP;, будет легко расширять объекты, которые могут быть использованы для построения пользовательского интерфейса, также как это сделано для добавления отдельных модулей, реализующих новые фильтры для &arts;. </para>
</sect1>
</chapter>
|