|
|
1.1 root 1: //
2: // nono
3: // Copyright (C) 2025 nono project
4: // Licensed under nono-license.txt
5: //
6:
7: //
8: // サウンドレンダラ
9: //
10:
1.1.1.2 ! root 11: // vtime / BLK_TSEC をブロック時刻と呼ぶことにする。
1.1 root 12: //
13: // 各サウンドトラックは 2 (=NSRCBLKS) ブロック分のバッファを持つ。
14: // ブロック境界の 000'000_nsec ちょうどにソースデバイスとサウンドレンダラの
15: // イベントが重なってどちらの順で実行しても構わないようにするため。
16: //
17: // サウンドレンダラのソース合成バッファは 1ブロックでいい (NMIXBLKS)。
18: //
19: // サウンドレンダラからホストドライバへの出力リングバッファは
20: // 4 (=NOUTBLKS) ブロック。少なくとも 3 あればいいのできりをよくする。
21:
22: #include "sound.h"
1.1.1.2 ! root 23: #include "bitmap.h"
1.1 root 24: #include "event.h"
25: #include "hostsound.h"
26: #include "mainapp.h"
27: #include "monitor.h"
28: #include "scheduler.h"
29: #include "syncer.h"
30: #include <algorithm>
31: #include <cmath>
32:
33: #define SAMPLES_TO_BYTES(samples) ((samples) * sizeof(int16))
34: #define SAMPLES_TO_FRAMES(samples) ((samples) / NCHAN)
35: #define FRAMES_TO_SAMPLES(frames) ((frames) * NCHAN)
36:
37: //
38: // サウンドトラック
39: //
40:
41: // コンストラクタ
42: SoundTrack::SoundTrack(Device *dev_, size_t mixer_samples_per_blk)
43: {
44: dev = dev_;
45: samples_per_blk = mixer_samples_per_blk;
46:
47: // 例外はこれをコンストラクトするところで catch してある。
48: buf.reset(new int16[samples_per_blk * NSRCBLKS]);
49:
50: std::fill(blkseq.begin(), blkseq.end(), (uint64)-1);
51: SoundRenderer::ClearDBFS(dbfs);
52: }
53:
54: // バッファの idx 番目のブロック先頭を返す。
55: // 時刻や使用状況によらない。
56: int16 *
57: SoundTrack::GetBlockByIdx(uint32 idx)
58: {
59: assert(idx < NSRCBLKS);
60: auto p = buf.get() + idx * samples_per_blk;
61: return p;
62: }
63:
64: // ブロック時刻 vseq のブロック先頭を返す。有効でなければ NULL を返す。
65: int16 *
66: SoundTrack::GetBlockBySeq(uint64 vseq)
67: {
68: uint32 idx = vseq % NSRCBLKS;
69:
70: if (blkseq[idx] == vseq) {
71: return GetBlockByIdx(idx);
72: }
73: return NULL;
74: }
75:
76: // 時刻 vseq をブロックと紐付けて (すでに紐付いていれば何もしない)、
77: // このブロックの先頭を返す。
78: int16 *
79: SoundTrack::AllocSeq(uint64 vseq)
80: {
81: uint32 idx = vseq % NSRCBLKS;
82:
83: if (__predict_true(blkseq[idx] == (uint64)-1)) {
84: // バッファは消去済み。
85: blkseq[idx] = vseq;
86: } else if (blkseq[idx] != vseq) {
87: // バッファが未消去なので書きつぶす。(通常起きないはず)
88: ClearSeq(vseq);
89: blkseq[idx] = vseq;
90: }
91:
92: return GetBlockByIdx(idx);
93: }
94:
95: // 時刻 vseq のブロックの紐付けをクリアする。
96: // (サウンドスレッドから呼ばれるはず)
97: void
98: SoundTrack::ClearSeq(uint64 vseq)
99: {
100: uint32 idx = vseq % NSRCBLKS;
101:
102: int16 *blk = GetBlockByIdx(idx);
103: memset(blk, 0, SAMPLES_TO_BYTES(samples_per_blk));
104: blkseq[idx] = (int64)-1;
105: }
106:
107:
108: //
109: // サウンドレンダラ
110: //
111:
112: // コンストラクタ
113: SoundRenderer::SoundRenderer()
114: : inherited(OBJ_SOUND_RENDERER)
115: {
116: // ミキサー周波数は機種ごとに違う。
117: if (gMainApp.IsX68030()) {
118: // X68030 では 62.5kHz を使う。(adpcm.cpp 参照)
119: mixerfreq = 62500;
120: } else {
121: assert(false);
122: }
123:
124: ClearDBFS(master_dbfs);
125:
126: monitor = gMonitorManager->Regist(ID_MONITOR_SOUND, this);
1.1.1.2 ! root 127: monitor->SetCallback(&SoundRenderer::MonitorScreen,
! 128: &SoundRenderer::MonitorBitmap);
1.1 root 129: monitor->SetSize(40, 2);
130: }
131:
132: // デストラクタ
133: SoundRenderer::~SoundRenderer()
134: {
135: TerminateThread();
136: }
137:
138: bool
139: SoundRenderer::Create()
140: {
141: try {
142: hostsnd.reset(new HostSoundDevice(this));
143: } catch (...) { }
144: if ((bool)hostsnd == false) {
145: warnx("Failed to initialize HostSoundDevice at %s", __method__);
146: return false;
147: }
148:
149: return true;
150: }
151:
152: void
153: SoundRenderer::SetLogLevel(int loglevel_)
154: {
155: inherited::SetLogLevel(loglevel_);
156:
157: if ((bool)hostsnd) {
158: hostsnd->SetLogLevel(loglevel_);
159: }
160: }
161:
162: // 初期化
163: bool
164: SoundRenderer::Init()
165: {
166: syncer = GetSyncer();
167:
168: // トラック合成用バッファ。(1ブロック分)
1.1.1.2 ! root 169: auto mixer_frames_per_blk = (uint64)mixerfreq * BLK_TSEC / 1_sec;
1.1 root 170: mixer_samples_per_blk = FRAMES_TO_SAMPLES(mixer_frames_per_blk);
171: mixbuf.reset(new(std::nothrow) int16[mixer_samples_per_blk]);
172: if (mixbuf == NULL) {
1.1.1.2 ! root 173: warnx("Could not allocate %zu bytes at %s",
1.1 root 174: SAMPLES_TO_BYTES(mixer_samples_per_blk), __method__);
175: return false;
176: }
177:
178: // 出力バッファなど実行時にも変わるものを設定。
179: request_outfreq = mixerfreq; // XXX 周波数変換はまだ
180: if (Reconfig(true) == false) {
1.1.1.2 ! root 181: // エラーメッセージは表示済み。
1.1 root 182: return false;
183: }
184:
185: auto evman = GetEventManager();
186: play_event = evman->Regist(this,
187: ToEventCallback(&SoundRenderer::Callback),
188: "Sound Renderer");
189:
190: return true;
191: }
192:
193: // リセット
194: void
195: SoundRenderer::ResetHard(bool poweron)
196: {
197: // 各デバイスがリセット時に Stop*() を呼ぶので
198: // 自動的にこっちも停止するはず。
199: }
200:
201: // 出力周波数かホストドライバの変更 (サウンドスレッドで呼ばれる)。
202: // エラーが起きた場合、
203: // 起動時なら、warn() 等で表示して false を返す(終了する)。
204: // 実行中なら、warn() 等で表示してもいいが、必ず None にフォールバックして
205: // true を返すこと。
206: bool
207: SoundRenderer::Reconfig(bool startup)
208: {
209: assert(hostsnd->IsQEmpty());
210:
211: // 出力周波数が変わったら出力リングバッファをリサイズ。
212: // outbuf は NOUTBLKS ブロック分。
213: bool need_resize = false;
214: if (outfreq != request_outfreq) {
215: outfreq = request_outfreq;
216: need_resize = true;
217: }
218:
1.1.1.2 ! root 219: auto out_frames_per_blk = (uint64)outfreq * BLK_TSEC / 1_sec;
1.1 root 220: out_samples_per_blk = FRAMES_TO_SAMPLES(out_frames_per_blk);
221:
222: if (need_resize) {
223: auto samples = out_samples_per_blk * NOUTBLKS;
224: outbuf.reset(new(std::nothrow) int16[samples]);
225: if (outbuf == NULL) {
1.1.1.2 ! root 226: warnx("Could not allocate %zu bytes at %s",
1.1 root 227: SAMPLES_TO_BYTES(samples), __method__);
228: return false;
229: }
230: }
231:
232: // ホストドライバも必ず再切替になる。
233: if (hostsnd->NotifyReselectDriver() == false) {
1.1.1.2 ! root 234: warn("%s: NotifyReselectDriver failed?", __method__);
1.1 root 235: return false;
236: }
237:
238: return true;
239: }
240:
241: // サウンドトラック(ソースデバイス)を登録する。
242: // 登録された SoundTrack へのポインタを返す。
243: // 各ソースデバイスが Init() で呼ぶ。
244: // Unregist 操作はない。
245: SoundTrack *
246: SoundRenderer::RegistTrack(Device *dev)
247: {
248: assert(tracks.size() < MAX_SOUND_SOURCES);
249:
250: try {
251: // この emplace_back が new SoundTrack(..)。
252: tracks.emplace_back(dev, mixer_samples_per_blk);
253: } catch (...) {
254: warnx("Failed to initialize SoundTrack");
255: return NULL;
256: }
257:
258: monitor->AddHeight(3);
259:
260: return &tracks.back();
261: }
262:
263: // 再生を開始する。
264: // 各サウンドデバイスが呼ぶ。
265: void
266: SoundRenderer::StartPlay(SoundTrack *track)
267: {
268: if (track->status != Running) {
269: putlog(1, "Playing [%s]", track->GetName().c_str());
270: track->status = Running;
271: }
272: if (play_event->IsRunning() == false) {
273: // ミキサーイベントが停止してたらイベントを起動する。
274: // 最初の1回は、ブロック境界になるようにする。
1.1.1.2 ! root 275: play_event->time = BLK_TSEC - (scheduler->GetVirtTime() % BLK_TSEC);
1.1 root 276:
277: scheduler->StartEvent(play_event);
278: syncer->NotifySoundRunning(true);
279: }
280: }
281:
282: // 再生を停止する。
283: // 各サウンドデバイスが呼ぶ。
284: void
285: SoundRenderer::StopPlay(SoundTrack *track)
286: {
287: if (track->status == Running) {
288: putlog(1, "Draining [%s]", track->GetName().c_str());
289: track->status = Draining;
290: }
291: }
292:
293: // ブロック先頭の時間に呼ばれて、直前のブロックを処理するコールバック。
294: // (VM スレッドで呼ばれる)
295: void
296: SoundRenderer::Callback(Event *event)
297: {
298: // デバイスが全員再生停止していれば
299: // 裏スレッドへの通知も次のイベントループも不要でここで終了。
300: bool playing = false;
301: for (const auto& track : tracks) {
302: if (track.status != Stopped) {
303: playing = true;
304: break;
305: }
306: }
307: if (playing == false) {
308: syncer->NotifySoundRunning(false);
309: return;
310: }
311:
312: // ブロック境界を繰り上がったところで呼ばれているので
313: // 処理したいのは一つ前のブロック。
1.1.1.2 ! root 314: uint64 vseq = (scheduler->GetVirtTime() / BLK_TSEC) - 1;
1.1 root 315:
316: // 裏(レンダラ)スレッドに通知するだけ。
317: // キューが一杯ならロストするだけ (だけ?)。
318: {
319: std::lock_guard<std::mutex> lock(mtx);
320: request_q.Enqueue(vseq);
321: cv.notify_one();
322: }
323:
324: // 次のイベント。
1.1.1.2 ! root 325: event->time = BLK_TSEC;
1.1 root 326: scheduler->StartEvent(event);
327: }
328:
329: void
330: SoundRenderer::Terminate()
331: {
332: std::lock_guard<std::mutex> lock(mtx);
333: request |= REQ_TERMINATE;
334: cv.notify_one();
335: }
336:
337: // サウンドスレッド
338: void
339: SoundRenderer::ThreadRun()
340: {
341: SetThreadAffinityHint(AffinityClass::Heavy);
342:
343: for (;;) {
344: uint32 action = 0;
345: uint64 vseq = -1;
346: {
347: std::unique_lock<std::mutex> lock(mtx);
348: cv.wait(lock, [&]() {
349: // 終了フラグは最優先。
350: if (__predict_false((request & REQ_TERMINATE))) {
351: action = REQ_TERMINATE;
352: return true;
353: }
354:
355: // ドライバ変更時は出力キューが空である必要がある。
356: // 入力キューより優先。
357: if (__predict_false((request & REQ_RECONFIG))) {
358: if (hostsnd->IsQEmpty()) {
359: request &= ~REQ_RECONFIG;
360: action = REQ_RECONFIG;
361: return true;
362: } else {
363: return false;
364: }
365: }
366:
367: // 入力キューがあれば。
368: if (request_q.Dequeue(&vseq)) {
369: return true;
370: }
371: return false;
372: });
373: }
374: // action がここで処理すべきフラグ。
375: // request のほうは必要なら下げてある。
376:
377: // action == REQ_TERMINATE (終了要求) か
378: // action == REQ_SELECT_DRIVER (ドライバ変更要求) か、
379: // どちらでもなければ vseq >= 0 ならデータあり。
380:
381: if (__predict_false((action & REQ_TERMINATE))) {
382: break;
383: } else if (__predict_false((action & REQ_RECONFIG))) {
384: Reconfig(false);
385: } else if ((int64)vseq >= 0) {
386: Mix(vseq);
387: }
388: }
389: }
390:
391: // デバイス間合成してホストに出力する。
392: // (Sound スレッド)
393: void
394: SoundRenderer::Mix(uint64 vseq)
395: {
396: int16 *dst;
397: uint dstbytes;
398:
399: // 周波数変換はまだないので直接出力バッファに書く。
400: if (1) {
401: dst = outbuf.get() + out_samples_per_blk * outpos;
402: dstbytes = SAMPLES_TO_BYTES(out_samples_per_blk);
403: }
404:
405: // ソースを合成。
406: bool first = true;
407: bool play = false;
408: for (auto& track : tracks) {
409: if (track.status == Stopped) {
410: continue;
411: }
412: if (track.status == Drained) {
413: // トラックが前回のブロックで終了したのでレベルメータを下げる。
414: // 時間軸が無限に流れていれば不要な処理だが、現在の実装だと
415: // 再生ブロックが終了すると自然とループ自体が止まる仕組みで、
416: // 再生ループが止まると最後のブロックのピークレベルから
417: // 書き換わらないことになってしまうので、ここで再生終了後の
418: // 次の無音ブロックを表現している。
419: ClearDBFS(track.dbfs);
420: track.status = Stopped;
421: continue;
422: }
423: play = true;
424:
425: // このブロック時刻に対応するバッファがあるか。
426: int16 *src = track.GetBlockBySeq(vseq);
427: if (src) {
428: // デバイスごと生波形についての処理。
429:
430: // レベルを計算。
431: CalcDBFS(track.dbfs, src);
432:
433: // 合成。
434: if (first) {
435: memcpy(dst, src, dstbytes);
436: first = false;
437: } else {
438: AddPCM(dst, src);
439: }
440:
441: // デバイスバッファをクリア。
442: // 排他制御はしていないので一周以上遅れると死ぬ
443: // (がその状況ではどのみち死ぬのでもう一緒)。
444: track.ClearSeq(vseq);
445: }
446:
447: // Draining なら最後のブロックを処理したので Drained に移行。
448: // 対応バッファがなくてももう出来ることはないので停止。
449: if (__predict_false(track.status == Draining)) {
450: // 表示上はここで停止でいいか。
451: putlog(1, "Stopped [%s]", track.GetName().c_str());
452: track.status = Drained;
453: }
454: }
455:
456: if (__predict_false(first)) {
457: memset(dst, 0, dstbytes);
458: }
459:
460: // マスターレベルを計算。
461: // ここまでは Drained のトラックがいても行う。
462: // 再生が終わった次のブロックでメータをゼロにするため。
463: CalcDBFS(master_dbfs, dst);
464:
465: // 1トラック以上が Playing ならホストに出力。
466: if (play) {
467: hostsnd->Play(dst);
468: outpos = (outpos + 1) % NOUTBLKS;
469: }
470: }
471:
472: // 1ブロックの PCM の合成を行う。dst = dst + src;
473: // PCM データはスケールが 1/MAX_SOUND_SOURCES 倍してあるため、
474: // 16ビットのまま加算しても溢れない。
475: void
476: SoundRenderer::AddPCM(int16 *d, const int16 *s)
477: {
478: for (uint i = 0; i < mixer_samples_per_blk; i++) {
479: *d = *d + *s;
480: d++;
481: s++;
482: }
483: }
484:
485: // 出力バッファ 1ブロックのバイト数を返す。
486: uint
487: SoundRenderer::GetOutBytesPerBlk() const noexcept
488: {
489: return SAMPLES_TO_BYTES(out_samples_per_blk);
490: }
491:
492: // src 1ブロックからチャンネルごとの dbFS を求めて dbfs に書き戻す。
493: void
494: SoundRenderer::CalcDBFS(std::array<double, NCHAN>& dbfs, const int16 *src)
495: {
496: // RMS とピークとあるが、ここではピークを表示する。
497:
498: std::array<int32, NCHAN> ipeak {};
499: for (const int16 *end = src + mixer_samples_per_blk; src < end; ) {
500: for (uint j = 0; j < NCHAN; j++) {
501: int16 s = *src++;
502: ipeak[j] = std::max(ipeak[j], (int32)std::abs(s));
503: }
504: }
505:
506: for (uint j = 0; j < NCHAN; j++) {
507: // std::log10(0) の結果は「処理系定義」なので避ける。
508: dbfs[j] = (ipeak[j] == 0)
509: ? -INFINITY
510: : 20.0 * std::log10((double)ipeak[j] / 32768);
511: }
512: }
513:
514: // この dbfs をクリアする。
515: /*static*/ void
516: SoundRenderer::ClearDBFS(std::array<double, NCHAN>& dbfs)
517: {
518: std::fill(dbfs.begin(), dbfs.end(), -INFINITY);
519: }
520:
521: void
1.1.1.2 ! root 522: SoundRenderer::MonitorScreen(Monitor *, TextScreen& screen)
1.1 root 523: {
524: int y;
525:
526: screen.Clear();
527:
528: // 0123456789012345678901234567890123456789
529: // Master L:01234567890123456789
530: // R:GGGGGGGGGGGGGOOOOORR
531: //
532: // [ADPCM] Running L:
533:
534: static const char * const statstr[] = {
535: "Stopped",
536: "Drained",
537: "Running",
538: "Draining",
539: };
540:
541: y = 0;
542: screen.Print(0, y, "Master");
1.1.1.2 ! root 543: MonitorScreenLevelMeter(screen, 18, y, master_dbfs);
1.1 root 544: y += NCHAN;
545:
546: std::string hline(screen.GetCol(), '-');
547: screen.Puts(0, y++, TA::Disable, hline.c_str());
548:
549: for (const auto& track : tracks) {
550: screen.Print(0, y, "%s", track.GetName().c_str());
551: screen.Print(6, y, "[%s]", statstr[track.status]);
1.1.1.2 ! root 552: MonitorScreenLevelMeter(screen, 18, y, track.dbfs);
1.1 root 553: y += (NCHAN + 1);
554: }
555: }
556:
557: void
1.1.1.2 ! root 558: SoundRenderer::MonitorScreenLevelMeter(TextScreen& screen,
1.1 root 559: int x, int y, const std::array<double, NCHAN> dbfs)
560: {
561: screen.Puts(x, y + 0, "L:");
562: screen.Puts(x, y + 1, "R:");
563: x += 2;
564:
565: for (uint ch = 0; ch < NCHAN; ch++) {
566: // 下地。
567: uint16 bg = ' ' | TA::Off;
568: for (uint i = 0; i < METER_LEN; i++) {
569: screen.Putc(x + i, y + ch, bg);
570: }
571:
572: // 最小の振幅は -90.3dbFS なので、これ以下は無音。
573: if (dbfs[ch] < -91) {
574: continue;
575: }
576: // プラス方向に切り上げて丸める。
577: int idbfs = (int)std::ceil(dbfs[ch]);
578: // -60dbFS 付近以下は1レベルにまとめる。
579: int width = METER_LEN + idbfs / METER_DIV;
580: if (width < 1) {
581: width = 1;
582: }
583:
584: uint16 fg = ' ' | TA::ReverseGreen;
585: for (uint i = 0; i < width; i++) {
586: // -20dbFS 以上は黄色(オレンジ)、-6dbFS 以上は赤。
587: if (i == (METER_LEN - (20 / METER_DIV))) {
588: fg = ' ' | TA::ReverseOrange;
589: } else if (i == (METER_LEN - (6 / METER_DIV))) {
590: fg = ' ' | TA::ReverseRed;
591: }
592: screen.Putc(x + i, y + ch, fg);
593: }
594: }
595: }
1.1.1.2 ! root 596:
! 597: void
! 598: SoundRenderer::MonitorBitmap(Monitor *mon, BitmapRGBX& bitmap)
! 599: {
! 600: // テキストで描いたレベルメータに区切りを入れる。
! 601:
! 602: uint fw = mon->GetFontWidth();
! 603: uint fh = mon->GetFontHeight();
! 604:
! 605: uint x = 20;
! 606: uint yend = (1 + tracks.size()) * 3;
! 607: for (uint y = 0; y < yend; y += 3) {
! 608: // L,R を区切る横線
! 609: uint px = mon->BX(x);
! 610: uint py = mon->BY(y + 1);
! 611: bitmap.DrawLineH(BGPANEL, px, py, px + fw * METER_LEN);
! 612:
! 613: // メモリを区切る縦線 (高さ 2CH 分)
! 614: for (uint i = 0; i < METER_LEN; i++) {
! 615: px = mon->BX(x + i) + fw - 1;
! 616: py = mon->BY(y);
! 617: bitmap.DrawLineV(BGPANEL, px, py, py + fh * 2);
! 618: }
! 619: }
! 620: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.