From 1707713bc89c7dcfdcedd6b54deed24ed2dc2580 Mon Sep 17 00:00:00 2001 From: Rerrah Date: Sat, 1 Nov 2025 22:06:26 +0900 Subject: [PATCH 1/2] Add YM2612 emulator --- wasm-src/src/chip/ym2612.cpp | 235 +++++++++++++++++++++++++ wasm-src/src/chip/ym2612.hpp | 41 +++++ wasm-src/src/{synth2.cpp => synth.cpp} | 35 ++-- 3 files changed, 296 insertions(+), 15 deletions(-) create mode 100644 wasm-src/src/chip/ym2612.cpp create mode 100644 wasm-src/src/chip/ym2612.hpp rename wasm-src/src/{synth2.cpp => synth.cpp} (93%) diff --git a/wasm-src/src/chip/ym2612.cpp b/wasm-src/src/chip/ym2612.cpp new file mode 100644 index 0000000..f7f37cc --- /dev/null +++ b/wasm-src/src/chip/ym2612.cpp @@ -0,0 +1,235 @@ +// SPDX-FileCopyrightText: 2025 Rerrah +// SPDX-License-Identifier: MIT + +#include "ym2612.hpp" + +#include +#include + +#include "ymfm_opn.h" + +namespace { +ymfm::ymfm_interface ymfm_common_interface{}; + +constexpr std::uint32_t kClock{7'670'453}; + +std::uint16_t PitchToBlockAndFNumber(const synth::Pitch& pitch) { + static std::int16_t fnum_table[12] = { + 0x0284, 0x02AA, 0x02D3, 0x02FE, 0x032B, 0x035B, + 0x038E, 0x03C5, 0x03FE, 0x043B, 0x047B, 0x04BF, + }; + + return (pitch.octave << 11) | fnum_table[static_cast(pitch.semitone)]; +} + +constexpr std::uint8_t kSlotFlags = 0xf0; +constexpr std::uint8_t kHighChannelKeyOnFlag = 0x04; +constexpr std::uint8_t kCenterPanning = 0xc0; + +class Ym2612InstrumentView { + public: + explicit Ym2612InstrumentView(const synth::FmInstrument& instrument) noexcept + : instrument_(instrument) {} + + std::uint8_t value_fb_al() const noexcept { + return ((instrument_.fb & 0x7) << 3) | (instrument_.al & 0x7); + } + + std::uint8_t value_lfo_freq() const noexcept { + return instrument_.lfo_freq & 0xf; + } + + std::uint8_t value_ams_pms() const noexcept { + return (instrument_.ams & 0x3) << 4 | (instrument_.pms & 0x7); + } + + class OperatorView; + + OperatorView operator_view(std::size_t index) const { + return OperatorView(instrument_.op.at(index)); + } + + class OperatorView { + public: + std::uint8_t value_dt_ml() const noexcept { + return (op_.dt & 0x7) << 4 | (op_.ml & 0xF); + } + + std::uint8_t value_ks_ar() const noexcept { + return (op_.ks & 0x3) << 6 | (op_.ar & 0x1F); + } + + std::uint8_t value_am_dr() const noexcept { + return (op_.am ? 0x80 : 0) | (op_.dr & 0x1F); + } + + std::uint8_t value_sl_rr() const noexcept { + return (op_.sl & 0xF) << 4 | (op_.rr & 0xF); + } + + std::uint8_t value_sr() const noexcept { return op_.sr & 0x1F; } + + std::uint8_t value_ssg_eg() const noexcept { return op_.ssg_eg & 0xF; } + + std::uint8_t value_tl() const noexcept { return op_.tl & 0x7F; } + + private: + explicit OperatorView(const synth::FmOperator& op) noexcept : op_(op) {} + + const synth::FmOperator& op_; + + friend OperatorView Ym2612InstrumentView::operator_view( + std::size_t index) const; + }; + + private: + const synth::FmInstrument& instrument_; +}; +} // namespace + +namespace synth::chip { +Ym2612::Ym2612() + : chip_(std::make_unique(ymfm_common_interface)) { + Reset(); +} + +Ym2612::~Ym2612() = default; + +bool Ym2612::Reset() { + if (!chip_) { + return false; + } + + chip_->reset(); + + WriteLow(0x27, 0); // Disable FM 3ch extended mode + + for (std::uint8_t ch = 0; ch < 3; ++ch) { + WriteLow(0xB4 + ch, kCenterPanning); + WriteHigh(0xB4 + ch, kCenterPanning); + } + + return true; +} + +std::uint32_t Ym2612::sampling_rate() const { + return chip_ ? chip_->sample_rate(kClock) : 0; +} + +bool Ym2612::KeyOn(std::uint8_t ch, const Pitch& pitch) { + if (!chip_) { + return false; + } + + std::uint16_t block_and_fnum = PitchToBlockAndFNumber(pitch); + + if (ch < 3) { + WriteLow(0xA4 + ch, block_and_fnum >> 8); + WriteLow(0xA0 + ch, block_and_fnum & 0xFF); + WriteLow(0x28, kSlotFlags | ch); + } else if (ch < 6) { + const auto high_ch = ch - 3; + WriteHigh(0xA4 + high_ch, block_and_fnum >> 8); + WriteHigh(0xA0 + high_ch, block_and_fnum & 0xFF); + WriteLow(0x28, kSlotFlags | kHighChannelKeyOnFlag | high_ch); + } + + return true; +} + +bool Ym2612::KeyOff(std::uint8_t ch) { + if (!chip_) { + return false; + } + + if (ch < 3) { + WriteLow(0x28, ~kSlotFlags & ch); + } else if (ch < 6) { + const auto high_ch = ch - 3; + WriteLow(0x28, ~kSlotFlags & (kHighChannelKeyOnFlag | high_ch)); + } + + return true; +} + +bool Ym2612::SetInstrument(const FmInstrument& instrument) { + if (!chip_) { + return false; + } + + WriteLow(0x22, instrument.lfo_freq); + + const auto write_low_and_high = [this](std::uint8_t address, + std::uint8_t data) { + WriteLow(address, data); + WriteHigh(address, data); + }; + + Ym2612InstrumentView inst_view{instrument}; + + for (std::uint8_t ch_offset = 0; ch_offset < 3; ++ch_offset) { + write_low_and_high(0xB0 + ch_offset, inst_view.value_fb_al()); + + write_low_and_high(0xB4 + ch_offset, + kCenterPanning | inst_view.value_ams_pms()); + + static const std::array kOpOffset{0x0, 0x8, 0x4, 0xC}; + for (std::size_t i = 0; i < 4; ++i) { + std::uint8_t op_offset = kOpOffset.at(i); + auto op_view = inst_view.operator_view(i); + + const auto write_op = [write_low_and_high, ch_offset, op_offset]( + std::uint8_t address, std::uint8_t data) { + write_low_and_high(address + ch_offset + op_offset, data); + }; + + write_op(0x30, op_view.value_dt_ml()); + write_op(0x40, op_view.value_tl()); + write_op(0x50, op_view.value_ks_ar()); + write_op(0x60, op_view.value_am_dr()); + write_op(0x70, op_view.value_sr()); + write_op(0x80, op_view.value_sl_rr()); + write_op(0x90, op_view.value_ssg_eg()); + } + } + + return true; +} + +void Ym2612::WriteLow(std::uint8_t address, std::uint8_t data) { + // if (!chip_) { + // return; + // } + + chip_->write_address(address); + chip_->write_data(data); +} + +void Ym2612::WriteHigh(std::uint8_t address, std::uint8_t data) { + // if (!chip_) { + // return; + // } + + chip_->write_address_hi(address & 0xFF); + chip_->write_data_hi(data); +} + +bool Ym2612::Generate(float* left_buffer, float* right_buffer, + std::uint32_t num_samples) { + if (!chip_) { + return false; + } + + ymfm::ym2612::output_data output; + for (std::uint32_t i = 0; i < num_samples; ++i) { + chip_->generate(&output); + + static constexpr float kNormalizeFactor = + std::numeric_limits::max(); + left_buffer[i] = output.data[0] / kNormalizeFactor; + right_buffer[i] = output.data[1] / kNormalizeFactor; + } + + return true; +} +} // namespace synth::chip diff --git a/wasm-src/src/chip/ym2612.hpp b/wasm-src/src/chip/ym2612.hpp new file mode 100644 index 0000000..d871bae --- /dev/null +++ b/wasm-src/src/chip/ym2612.hpp @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2025 Rerrah +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include + +#include "../instrument.hpp" +#include "../pitch.hpp" + +namespace ymfm { +class ym2612; +} + +namespace synth::chip { +class Ym2612 { + public: + Ym2612(); + ~Ym2612(); + + bool Reset(); + + std::uint32_t sampling_rate() const; + std::uint8_t num_channels() const { return 6; } + + bool KeyOn(std::uint8_t ch, const Pitch& pitch); + bool KeyOff(std::uint8_t ch); + + bool SetInstrument(const FmInstrument& instrument); + + bool Generate(float* left_buffer, float* right_buffer, + std::uint32_t num_samples); + + private: + std::unique_ptr chip_; + + void WriteLow(std::uint8_t address, std::uint8_t data); + void WriteHigh(std::uint8_t address, std::uint8_t data); +}; +} // namespace synth::chip diff --git a/wasm-src/src/synth2.cpp b/wasm-src/src/synth.cpp similarity index 93% rename from wasm-src/src/synth2.cpp rename to wasm-src/src/synth.cpp index d73da2c..c97a04b 100644 --- a/wasm-src/src/synth2.cpp +++ b/wasm-src/src/synth.cpp @@ -14,6 +14,7 @@ #include #include "chip/ym2608.hpp" +#include "chip/ym2612.hpp" #include "instrument.hpp" #include "keyboard.hpp" #include "miniaudio.h" @@ -21,7 +22,7 @@ namespace { // Chip instance. -using VariantChip = std::variant; +using VariantChip = std::variant; VariantChip chip_instance; // Temporary buffer. @@ -90,13 +91,11 @@ std::mutex chip_input_mutex, chip_output_mutex; namespace synth { enum ChipType { - Ym2203 = 0, - Ym2608 = 1, - Ym2610 = 2, - Ym2612 = 3, + Ym2608 = 0, + Ym2612 = 1, }; -bool SetSamplingRateInternal(std::uint32_t rate) { +bool initializeResampler(std::uint32_t rate) { ma_resampler_uninit(&left_resampler, nullptr); ma_resampler_uninit(&right_resampler, nullptr); @@ -111,26 +110,36 @@ bool SetSamplingRateInternal(std::uint32_t rate) { return false; } - sampling_rate = rate; return true; } bool SetSamplingRate(std::uint32_t rate) { std::scoped_lock lock(chip_input_mutex, chip_output_mutex); - return SetSamplingRateInternal(rate); + bool result = initializeResampler(rate); + if (result) { + sampling_rate = rate; + } + + return result; } -bool SwitchChip(ChipType type, std::uint32_t rate) { +bool ChangeChip(int type) { std::scoped_lock lock(chip_input_mutex, chip_output_mutex); - switch (type) { + switch (static_cast(type)) { case ChipType::Ym2608: if (!std::holds_alternative(chip_instance)) { chip_instance.emplace(); } break; + case ChipType::Ym2612: + if (!std::holds_alternative(chip_instance)) { + chip_instance.emplace(); + } + break; + default: // Unsupported chip type. return false; @@ -141,11 +150,7 @@ bool SwitchChip(ChipType type, std::uint32_t rate) { [](const auto& chip) { return chip.num_channels(); }, chip_instance); keyboard = Keyboard(num_channels); - return SetSamplingRateInternal(rate); -} - -bool ChangeChip(int type) { - return SwitchChip(static_cast(type), sampling_rate); + return initializeResampler(sampling_rate); } bool Initialize() { return ChangeChip(ChipType::Ym2608); } From c0030e32b99f6117222baacb6fe8376c5c6ac7e1 Mon Sep 17 00:00:00 2001 From: Rerrah Date: Sun, 2 Nov 2025 11:24:17 +0900 Subject: [PATCH 2/2] Support YM2612 preview --- .../[id]/components/audio-preview.tsx | 25 ++++++++++++------ features/preview/consts.ts | 12 ++++++++- features/preview/synth.ts | 20 ++++++++++++++ features/preview/types.ts | 2 +- public/wasm/synth.wasm | Bin 173894 -> 179240 bytes public/worker/worker.js | 21 +++++++++++++++ stores/search.ts | 4 +-- stores/synth-settings.ts | 12 +++++++++ 8 files changed, 84 insertions(+), 12 deletions(-) create mode 100644 stores/synth-settings.ts diff --git a/app/instrument/[id]/components/audio-preview.tsx b/app/instrument/[id]/components/audio-preview.tsx index a611ee1..063eeeb 100644 --- a/app/instrument/[id]/components/audio-preview.tsx +++ b/app/instrument/[id]/components/audio-preview.tsx @@ -15,10 +15,11 @@ import { } from "@/components/ui/select"; import { useFmSynthesizer } from "@/contexts/synth"; import type { FmInstrument } from "@/features/instrument/types"; -import { AVAILABLE_CHIPS } from "@/features/preview/consts"; +import { AVAILABLE_CHIP_MAP } from "@/features/preview/consts"; import { clampOctave, withinOctave } from "@/features/preview/octave"; import { type Pitch, pitchToString } from "@/features/preview/pitch"; import type { AvailableChip } from "@/features/preview/types"; +import { useSynthSettings } from "@/stores/synth-settings"; import { iota } from "@/utils/range"; const DISPLAYED_OCTAVE_RANGE = 4; @@ -42,7 +43,7 @@ export function AudioPreview({ }: { instrument: FmInstrument; }): React.JSX.Element { - const [playbackChip, setPlaybackChip] = useState("OPNA"); + const { playbackChip, setPlaybackChip } = useSynthSettings(); const [octaveOffset, setOctaveOffset] = useState(3); // Key: pointerId, Value: ActiveNote @@ -66,6 +67,12 @@ export function AudioPreview({ }; }, [synthContext]); + function handlePlaybackChipValueChanged(value: string) { + const chipType = value as AvailableChip; + synthContext?.changeChip(chipType); + setPlaybackChip(chipType); + } + async function noteOn(pitch: Pitch, pointerId: number) { await synthContext?.keyOn(pitch, pointerId); } @@ -161,17 +168,19 @@ export function AudioPreview({ diff --git a/features/preview/consts.ts b/features/preview/consts.ts index dd2148a..1bb1b5e 100644 --- a/features/preview/consts.ts +++ b/features/preview/consts.ts @@ -1,3 +1,13 @@ import type { AvailableChip } from "./types"; -export const AVAILABLE_CHIPS: AvailableChip[] = ["OPNA"] as const; +/** + * Map of available sound chips and their corresponding values. + * The values must match the ones defined in WASM. + */ +export const AVAILABLE_CHIP_MAP: ReadonlyMap = new Map< + AvailableChip, + number +>([ + ["OPNA", 0], + ["OPN2", 1], +]); diff --git a/features/preview/synth.ts b/features/preview/synth.ts index 0600dec..8aa2ec4 100644 --- a/features/preview/synth.ts +++ b/features/preview/synth.ts @@ -1,6 +1,8 @@ import { useEffect, useRef } from "react"; import type { FmInstrument } from "../instrument/types"; +import { AVAILABLE_CHIP_MAP } from "./consts"; import type { Pitch } from "./pitch"; +import type { AvailableChip } from "./types"; export type Synthesizer = { audioContext: AudioContext; @@ -12,6 +14,7 @@ export type Synthesizer = { keyOff: (id: number) => void; setInstrument: (instrument: FmInstrument) => void; reset: () => void; + changeChip: (chip: AvailableChip) => void; }; const RING_BUFFER_SIZE = 8192; @@ -44,6 +47,11 @@ type ResetWorkerRequestMessage = { type: "reset"; }; +type ChangeChipWorkerRequestMessage = { + type: "changeChip"; + chipId: number; +}; + type LoadWasmWorkerRequestMessage = { type: "loadWasm"; loaderUrl: string; @@ -234,6 +242,18 @@ export function useSynthesizer() { type: "reset", } satisfies ResetWorkerRequestMessage); }, + + changeChip: (chip) => { + const chipId = AVAILABLE_CHIP_MAP.get(chip); + if (chipId === undefined) { + return; + } + + wasmWorker.postMessage({ + type: "changeChip", + chipId, + } satisfies ChangeChipWorkerRequestMessage); + }, }; })(); diff --git a/features/preview/types.ts b/features/preview/types.ts index b54f8ec..3731733 100644 --- a/features/preview/types.ts +++ b/features/preview/types.ts @@ -2,4 +2,4 @@ import type { RecommendedChip } from "../instrument/models"; type Subset = U; -export type AvailableChip = Subset; +export type AvailableChip = Subset; diff --git a/public/wasm/synth.wasm b/public/wasm/synth.wasm index 1d8a423cd41e390e4fab8ecd66c49a7d426d2947..09bf8837e3f5936cb0cc8925a4a576938e026387 100644 GIT binary patch delta 30667 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 delta 27893 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 Gfc_tAMY7QV diff --git a/public/worker/worker.js b/public/worker/worker.js index ba54c99..ff2e79d 100644 --- a/public/worker/worker.js +++ b/public/worker/worker.js @@ -2,6 +2,8 @@ importScripts("/wasm/synth.js"); let wasm; +let currentInstrument; + /** @type {Int32Array} */ let control; const SAB_CONTROL_SIZE = 2; @@ -127,6 +129,7 @@ function handleSetInstrument(instrument) { } wasm.setInstrument(instrument); + currentInstrument = instrument; } /** @@ -197,6 +200,20 @@ function handleGenerate() { Atomics.store(control, SAB_WRITE_POSITION_INDEX, pos); } +function handleChangeChip(id) { + if (!wasm) { + self.postMessage(WASM_NOT_LOADED_MESSAGE); + return; + } + + wasm.changeChip(id); + messageQueue.length = 0; + + if (currentInstrument) { + wasm.setInstrument(currentInstrument); + } +} + self.onmessage = async ({ data }) => { switch (data.type) { case "loadWasm": @@ -217,6 +234,10 @@ self.onmessage = async ({ data }) => { handleReset(); break; + case "changeChip": + handleChangeChip(data.chipId); + break; + default: break; } diff --git a/stores/search.ts b/stores/search.ts index a93f59d..3b0d8a3 100644 --- a/stores/search.ts +++ b/stores/search.ts @@ -2,10 +2,10 @@ import { create } from "zustand"; export type ViewMode = "grid" | "list"; -interface SearchStore { +type SearchStore = { viewMode: ViewMode; setViewMode: (mode: ViewMode) => void; -} +}; export const useSearchStore = create((set) => ({ viewMode: "grid", diff --git a/stores/synth-settings.ts b/stores/synth-settings.ts new file mode 100644 index 0000000..3f37375 --- /dev/null +++ b/stores/synth-settings.ts @@ -0,0 +1,12 @@ +import { create } from "zustand"; +import type { AvailableChip } from "@/features/preview/types"; + +type SynthSettings = { + playbackChip: AvailableChip; + setPlaybackChip: (chip: AvailableChip) => void; +}; + +export const useSynthSettings = create((set) => ({ + playbackChip: "OPNA", + setPlaybackChip: (chip) => set({ playbackChip: chip }), +}));