File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / sound.h
Revision 1.1.1.2 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:06:01 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
nono 1.7.0

//
// nono
// Copyright (C) 2025 nono project
// Licensed under nono-license.txt
//

//
// サウンドレンダラ
//

#pragma once

#include "thread.h"
#include "fixedqueue.h"
#include <array>
#include <condition_variable>
#include <mutex>

class BitmapRGBX;
class Syncer;
class HostSoundDevice;

// 定数。
class Sound
{
 public:
	// (1フレームの)チャンネル数 (デバイスと SoundRenderer の間はステレオ固定)。
	static constexpr int NCHAN = 2;

	// 1フレームのバイト数。
	static constexpr size_t FRAME_BYTES = NCHAN * sizeof(int16);

	// 1ブロックの時間。
	// ADPCM の制約からこれ以上小さくは出来ない(し大きくする必要もない)。
	static constexpr uint64 BLK_TSEC = 40_msec;

	// サウンドソースのブロック数。
	// ダブルバッファ方式なので 2。
	static constexpr uint NSRCBLKS = 2;

	// 合成バッファのブロック数。
	// 1ブロック境界ごとに逐次処理するので 1。
	static constexpr uint NMIXBLKS = 1;

	// 出力バッファのブロック数。
	// 最低 3 ブロック必要だが、2 のべき乗だとお得。
	static constexpr uint NOUTBLKS = 4;
};

enum SoundStat {
	Stopped,	// デバイスは再生をしていない
	Drained,
	Running,	// デバイスは再生中
	Draining,	// デバイスはこのブロック処理後に再生を停止
};

// サウンドの 1 トラック (ソースデバイス) 分。
class SoundTrack : private Sound
{
 public:
	SoundTrack(Device *dev_, size_t frames_per_blk_);

	int16 *GetBlockBySeq(uint64 vseq);
	int16 *GetBlockByIdx(uint32 idx);
	int16 *AllocSeq(uint64 vseq);
	void ClearSeq(uint64 vseq);

	// ソースデバイスを返す。
	Device *GetDevice() const noexcept { return dev; }

	// ソースデバイス名を返す。
	const std::string& GetName() const { return dev->GetName(); }

	// 再生状態
	SoundStat status {};

	// レベル(L,R)。
	std::array<double, NCHAN> dbfs {};

 private:
	Device *dev {};

	// NBLKS 分のバッファ。
	std::unique_ptr<int16[]> buf {};

	// buf の各ブロックが有効ならそのシーケンス。-1 なら未使用。
	std::array<uint64, NSRCBLKS> blkseq {};

	// 1ブロックのサンプル数。
	size_t samples_per_blk {};
};

// サウンドレンダラ
class SoundRenderer : public ThreadDevice, private Sound
{
	using inherited = ThreadDevice;

 private:
	// ソースデバイスの最大数。2 のべき乗であること。
	static constexpr uint32 MAX_SOUND_SOURCES	= 4;

	static constexpr uint32 REQ_TERMINATE	= 0x80000000;
	static constexpr uint32 REQ_RECONFIG	= 0x00000001;

	static constexpr int METER_LEN = 20;	// メータ部分の文字数
	static constexpr int METER_DIV = 3;		// 1文字あたりの dbFS

 public:
	SoundRenderer();
	~SoundRenderer() override;

	bool Create() override;
	void SetLogLevel(int loglevel_) override;
	bool Init() override;
	void ResetHard(bool poweron) override;

	SoundTrack *RegistTrack(Device *);
	void StartPlay(SoundTrack *);
	void StopPlay(SoundTrack *);

	uint GetMixerFreq() const noexcept { return mixerfreq; }
	uint GetOutFrequency() const noexcept { return outfreq; }
	uint GetOutBytesPerBlk() const noexcept;

	static void ClearDBFS(std::array<double, NCHAN>& dbFS);

 protected:
	void ThreadRun() override;
	void Terminate() override;
	bool Reconfig(bool startup);

	void Callback(Event *);

	void Mix(uint64 vseq);
	void AddPCM(int16 *dst, const int16 *src);
	void CalcDBFS(std::array<double, NCHAN>& dbFS, const int16 *src);

	DECLARE_MONITOR_SCREEN(MonitorScreen);
	DECLARE_MONITOR_BITMAP(MonitorBitmap);
	void MonitorScreenLevelMeter(TextScreen&, int x, int y,
		const std::array<double, NCHAN> dbfs);

	// VM スレッドからの通知用。
	uint32 request {};
	std::mutex mtx {};
	std::condition_variable cv {};

	// VM スレッドからの処理依頼キュー。
	// キューにする必要ある?
	FixedQueue<uint64, NSRCBLKS> request_q {};

	// サウンドトラック(ソース)。
	std::vector<SoundTrack> tracks {};

	// サウンドミキサーのサンプリングレート [Hz]。
	// サウンドトラック(ソース)もこの周波数。機種によって異なる。
	uint mixerfreq {};

	// ミキサーバッファ 1ブロックのサンプル数。
	size_t mixer_samples_per_blk {};

	// 合成用バッファ。
	std::unique_ptr<int16[]> mixbuf {};

	// マスターレベル。
	std::array<double, NCHAN> master_dbfs {};

	// 出力サンプリングレート [Hz]
	uint outfreq {};

	// 出力バッファ 1ブロックのサンプル数。
	size_t out_samples_per_blk {};

	// 出力バッファ。
	std::unique_ptr<int16[]> outbuf {};

	// 現在の出力ブロック位置。
	uint outpos {};

	// 出力キュー。バッファの先頭だけで扱う。
	FixedQueue<const uint16 *, NOUTBLKS> output_q {};

	// 出力サンプリングレート変更指示。
	uint request_outfreq {};

	std::unique_ptr<HostSoundDevice> hostsnd /*{}*/;

	Event *play_event {};

	Monitor *monitor {};

	Syncer *syncer {};
};

inline SoundRenderer *GetSoundRenderer() {
	return Object::GetObject<SoundRenderer>(OBJ_SOUND_RENDERER);
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.