←Previous | 1 2 3 4 | Next→
//  故意にばらつかせてあると思われるが、どのように手を加えたのかは不明
//
//  Inside X68000にYM2151は3.579545MHzを与えたときに正確な音が出ると書かれているが、3.58MHzと書くべきである
//  カラーサブキャリア周波数である315/88=3.579545454...MHzを流用しやすいように設計されていることは間違いないが、
//  実際には3.58MHzを与えた方が正確なのだから、YM2151に与えるべき周波数としてカラーサブキャリア周波数を小数点以下6桁まで書くのは適切ではない
//    echo l=["C#","D","D#","E","F","F#","G","G#","A","A#","B","C"];a=[1299,1376,1458,1545,1637,1734,1837,1946,2062,2185,2315,2452];print("       T Hz    @3.579545MHz  Ecent   @3.58MHz    Ecent");s1=0;s2=0;for(n=-8,3,t=eval("440*2^(n/12)");o1=eval("a[9+n]*4*3.579545e+6/2^26");o2=eval("a[9+n]*4*3.58e+6/2^26");e1=eval("log(o1/t)/log(2^(1/12))")*100;e2=eval("log(o2/t)/log(2^(1/12))")*100;printf("%-2s  %10.6f  %10.6f  %+6.3f  %10.6f  %+6.3f%c",l[9+n],t,o1,e1,o2,e2,10);s1+=abs(e1);s2+=abs(e2));printf("                            %6.3f              %6.3f%c",s1/12,s2/12,10) | gp-2.5 -q
//           T Hz    @3.579545MHz  Ecent   @3.58MHz    Ecent
//    C#  277.182631  277.151403  -0.195  277.186632  +0.025
//    D   293.664768  293.579931  -0.500  293.617249  -0.280
//    D#  311.126984  311.075247  -0.288  311.114788  -0.068
//    E   329.627557  329.637350  +0.051  329.679251  +0.271
//    F   349.228231  349.266241  +0.188  349.310637  +0.408
//    F#  369.994423  369.961919  -0.152  370.008945  +0.068
//    G   391.995436  391.937743  -0.255  391.987562  -0.035
//    G#  415.304698  415.193711  -0.463  415.246487  -0.243
//    A   440.000000  439.943182  -0.224  439.999104  -0.004
//    A#  466.163762  466.186155  +0.083  466.245413  +0.303
//    B   493.883301  493.922631  +0.138  493.985415  +0.358
//    C   523.251131  523.152610  -0.326  523.219109  -0.106
//                                 0.239               0.181
//
const OPM_PG_BASE_TABLE = [
  //0x1f,0x20
  1299, 1300, 1301, 1302, 1303, 1304, 1305, 1306, 1308, 1309, 1310, 1311, 1313, 1314, 1315, 1316,
  1318, 1319, 1320, 1321, 1322, 1323, 1324, 1325, 1327, 1328, 1329, 1330, 1332, 1333, 1334, 1335,
  1337, 1338, 1339, 1340, 1341, 1342, 1343, 1344, 1346, 1347, 1348, 1349, 1351, 1352, 1353, 1354,
  1356, 1357, 1358, 1359, 1361, 1362, 1363, 1364, 1366, 1367, 1368, 1369, 1371, 1372, 1373, 1374,
  //0x21
  1376, 1377, 1378, 1379, 1381, 1382, 1383, 1384, 1386, 1387, 1388, 1389, 1391, 1392, 1393, 1394,
  1396, 1397, 1398, 1399, 1401, 1402, 1403, 1404, 1406, 1407, 1408, 1409, 1411, 1412, 1413, 1414,
  1416, 1417, 1418, 1419, 1421, 1422, 1423, 1424, 1426, 1427, 1429, 1430, 1431, 1432, 1434, 1435,
  1437, 1438, 1439, 1440, 1442, 1443, 1444, 1445, 1447, 1448, 1449, 1450, 1452, 1453, 1454, 1455,
  //0x22
  1458, 1459, 1460, 1461, 1463, 1464, 1465, 1466, 1468, 1469, 1471, 1472, 1473, 1474, 1476, 1477,
  1479, 1480, 1481, 1482, 1484, 1485, 1486, 1487, 1489, 1490, 1492, 1493, 1494, 1495, 1497, 1498,
  1501, 1502, 1503, 1504, 1506, 1507, 1509, 1510, 1512, 1513, 1514, 1515, 1517, 1518, 1520, 1521,
  1523, 1524, 1525, 1526, 1528, 1529, 1531, 1532, 1534, 1535, 1536, 1537, 1539, 1540, 1542, 1543,
  //0x23,0x24
  1545, 1546, 1547, 1548, 1550, 1551, 1553, 1554, 1556, 1557, 1558, 1559, 1561, 1562, 1564, 1565,
  1567, 1568, 1569, 1570, 1572, 1573, 1575, 1576, 1578, 1579, 1580, 1581, 1583, 1584, 1586, 1587,
  1590, 1591, 1592, 1593, 1595, 1596, 1598, 1599, 1601, 1602, 1604, 1605, 1607, 1608, 1609, 1610,
  1613, 1614, 1615, 1616, 1618, 1619, 1621, 1622, 1624, 1625, 1627, 1628, 1630, 1631, 1632, 1633,
  //0x25
  1637, 1638, 1639, 1640, 1642, 1643, 1645, 1646, 1648, 1649, 1651, 1652, 1654, 1655, 1656, 1657,
  1660, 1661, 1663, 1664, 1666, 1667, 1669, 1670, 1672, 1673, 1675, 1676, 1678, 1679, 1681, 1682,
  1685, 1686, 1688, 1689, 1691, 1692, 1694, 1695, 1697, 1698, 1700, 1701, 1703, 1704, 1706, 1707,
  1709, 1710, 1712, 1713, 1715, 1716, 1718, 1719, 1721, 1722, 1724, 1725, 1727, 1728, 1730, 1731,
  //0x26
  1734, 1735, 1737, 1738, 1740, 1741, 1743, 1744, 1746, 1748, 1749, 1751, 1752, 1754, 1755, 1757,
  1759, 1760, 1762, 1763, 1765, 1766, 1768, 1769, 1771, 1773, 1774, 1776, 1777, 1779, 1780, 1782,
  1785, 1786, 1788, 1789, 1791, 1793, 1794, 1796, 1798, 1799, 1801, 1802, 1804, 1806, 1807, 1809,
  1811, 1812, 1814, 1815, 1817, 1819, 1820, 1822, 1824, 1825, 1827, 1828, 1830, 1832, 1833, 1835,
  //0x27,0x28
  1837, 1838, 1840, 1841, 1843, 1845, 1846, 1848, 1850, 1851, 1853, 1854, 1856, 1858, 1859, 1861,
  1864, 1865, 1867, 1868, 1870, 1872, 1873, 1875, 1877, 1879, 1880, 1882, 1884, 1885, 1887, 1888,
  1891, 1892, 1894, 1895, 1897, 1899, 1900, 1902, 1904, 1906, 1907, 1909, 1911, 1912, 1914, 1915,
  1918, 1919, 1921, 1923, 1925, 1926, 1928, 1930, 1932, 1933, 1935, 1937, 1939, 1940, 1942, 1944,
  //0x29
  1946, 1947, 1949, 1951, 1953, 1954, 1956, 1958, 1960, 1961, 1963, 1965, 1967, 1968, 1970, 1972,
  1975, 1976, 1978, 1980, 1982, 1983, 1985, 1987, 1989, 1990, 1992, 1994, 1996, 1997, 1999, 2001,
  2003, 2004, 2006, 2008, 2010, 2011, 2013, 2015, 2017, 2019, 2021, 2022, 2024, 2026, 2028, 2029,
  2032, 2033, 2035, 2037, 2039, 2041, 2043, 2044, 2047, 2048, 2050, 2052, 2054, 2056, 2058, 2059,
  //0x2a
  2062, 2063, 2065, 2067, 2069, 2071, 2073, 2074, 2077, 2078, 2080, 2082, 2084, 2086, 2088, 2089,
  2092, 2093, 2095, 2097, 2099, 2101, 2103, 2104, 2107, 2108, 2110, 2112, 2114, 2116, 2118, 2119,
  2122, 2123, 2125, 2127, 2129, 2131, 2133, 2134, 2137, 2139, 2141, 2142, 2145, 2146, 2148, 2150,
  2153, 2154, 2156, 2158, 2160, 2162, 2164, 2165, 2168, 2170, 2172, 2173, 2176, 2177, 2179, 2181,
  //0x2b,0x2c
  2185, 2186, 2188, 2190, 2192, 2194, 2196, 2197, 2200, 2202, 2204, 2205, 2208, 2209, 2211, 2213,
  2216, 2218, 2220, 2222, 2223, 2226, 2227, 2230, 2232, 2234, 2236, 2238, 2239, 2242, 2243, 2246,
  2249, 2251, 2253, 2255, 2256, 2259, 2260, 2263, 2265, 2267, 2269, 2271, 2272, 2275, 2276, 2279,
  2281, 2283, 2285, 2287, 2288, 2291, 2292, 2295, 2297, 2299, 2301, 2303, 2304, 2307, 2308, 2311,
  //0x2d
  2315, 2317, 2319, 2321, 2322, 2325, 2326, 2329, 2331, 2333, 2335, 2337, 2338, 2341, 2342, 2345,
  2348, 2350, 2352, 2354, 2355, 2358, 2359, 2362, 2364, 2366, 2368, 2370, 2371, 2374, 2375, 2378,
  2382, 2384, 2386, 2388, 2389, 2392, 2393, 2396, 2398, 2400, 2402, 2404, 2407, 2410, 2411, 2414,
  2417, 2419, 2421, 2423, 2424, 2427, 2428, 2431, 2433, 2435, 2437, 2439, 2442, 2445, 2446, 2449,
  //0x2e
  2452, 2454, 2456, 2458, 2459, 2462, 2463, 2466, 2468, 2470, 2472, 2474, 2477, 2480, 2481, 2484,
  2488, 2490, 2492, 2494, 2495, 2498, 2499, 2502, 2504, 2506, 2508, 2510, 2513, 2516, 2517, 2520,
  2524, 2526, 2528, 2530, 2531, 2534, 2535, 2538, 2540, 2542, 2544, 2546, 2549, 2552, 2553, 2556,
  2561, 2563, 2565, 2567, 2568, 2571, 2572, 2575, 2577, 2579, 2581, 2583, 2586, 2589, 2590, 2593
  ];

//EG

//enum OPMStage
//  EGのステージ
const OPMStage_ATTACK  = 0;  //アタック
const OPMStage_DECAY   = 1;  //ファーストディケイ
const OPMStage_SUSTAIN = 2;  //セカンドディケイ
const OPMStage_RELEASE = 3;  //リリース
const OPMStage_SILENCE = 4;  //停止

//  速度の選択
//  opmEGCounterから3bit取り出すときのbit番号
//  大きいほど変化が遅くなる
const OPM_EG_SHIFT_TABLE = [
  //+0+1  +2  +3  +4  +5  +6  +7  +8  +9 +10 +11 +12 +13 +14 +15
  0 ,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  //0..15
  0 ,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  //16..31
  11, 11, 11, 11, 10, 10, 10, 10,  9,  9,  9,  9,  8,  8,  8,  8,  //32..47
  7 ,  7,  7,  7,  6,  6,  6,  6,  5,  5,  5,  5,  4,  4,  4,  4,  //48..63
  3 ,  3,  3,  3,  2,  2,  2,  2,  1,  1,  1,  1,  0,  0,  0,  0,  //64..79
  0 ,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  //80..95
  0 ,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  //96..111
  0 ,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0  //112..127
  ];

const OPM_EG_TABLE_X = [
  0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,  //0..7
  0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,  //8..15
  0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,  //16..23
  0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,  //24..31
  //32:0      33:8        34:16       35:24       36:0        37:8        38:16       39:24
  0x01010101, 0x01011101, 0x01110111, 0x01111111, 0x01010101, 0x01011101, 0x01110111, 0x01111111,  //32..39
  0x01010101, 0x01011101, 0x01110111, 0x01111111, 0x01010101, 0x01011101, 0x01110111, 0x01111111,  //40..47
  0x01010101, 0x01011101, 0x01110111, 0x01111111, 0x01010101, 0x01011101, 0x01110111, 0x01111111,  //48..55
  0x01010101, 0x01011101, 0x01110111, 0x01111111, 0x01010101, 0x01011101, 0x01110111, 0x01111111,  //56..63
  0x01010101, 0x01011101, 0x01110111, 0x01111111, 0x01010101, 0x01011101, 0x01110111, 0x01111111,  //64..71
  0x01010101, 0x01011101, 0x01110111, 0x01111111, 0x01010101, 0x01011101, 0x01110111, 0x01111111,  //72..79
  //80:32     81:40       82:48       83:56       84:64       85:72       86:80       87:88
  0x11111111, 0x11121112, 0x12121212, 0x12221222, 0x22222222, 0x22242224, 0x24242424, 0x24442444,  //80..87
  //88:96     89:104      90:112      91:120      92:128      93:128      94:128      95:128
  0x44444444, 0x44484448, 0x48484848, 0x48884888, 0x88888888, 0x88888888, 0x88888888, 0x88888888,  //88..95
  0x88888888, 0x88888888, 0x88888888, 0x88888888, 0x88888888, 0x88888888, 0x88888888, 0x88888888,  //96..103
  0x88888888, 0x88888888, 0x88888888, 0x88888888, 0x88888888, 0x88888888, 0x88888888, 0x88888888,  //104..111
  0x88888888, 0x88888888, 0x88888888, 0x88888888, 0x88888888, 0x88888888, 0x88888888, 0x88888888,  //112..119
  0x88888888, 0x88888888, 0x88888888, 0x88888888, 0x88888888, 0x88888888, 0x88888888, 0x88888888  //120..127
  ];
const OPM_DT1_BASE_TABLE = [
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 8, 8, 8, 8,
  1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 8, 8, 9, 10, 11, 12, 13, 14, 16, 16, 16, 16,
  2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 8, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 20, 22, 22, 22, 22
  ];

//LFO
const OPM_LFO_NOISE_BASE = [
  0xff, 0xee, 0xd3, 0x80, 0x58, 0xda, 0x7f, 0x94, 0x9e, 0xe3, 0xfa, 0x00, 0x4d, 0xfa, 0xff, 0x6a,
  0x7a, 0xde, 0x49, 0xf6, 0x00, 0x33, 0xbb, 0x63, 0x91, 0x60, 0x51, 0xff, 0x00, 0xd8, 0x7f, 0xde,
  0xdc, 0x73, 0x21, 0x85, 0xb2, 0x9c, 0x5d, 0x24, 0xcd, 0x91, 0x9e, 0x76, 0x7f, 0x20, 0xfb, 0xf3,
  0x00, 0xa6, 0x3e, 0x42, 0x27, 0x69, 0xae, 0x33, 0x45, 0x44, 0x11, 0x41, 0x72, 0x73, 0xdf, 0xa2,
  0x32, 0xbd, 0x7e, 0xa8, 0x13, 0xeb, 0xd3, 0x15, 0xdd, 0xfb, 0xc9, 0x9d, 0x61, 0x2f, 0xbe, 0x9d,
  0x23, 0x65, 0x51, 0x6a, 0x84, 0xf9, 0xc9, 0xd7, 0x23, 0xbf, 0x65, 0x19, 0xdc, 0x03, 0xf3, 0x24,
  0x33, 0xb6, 0x1e, 0x57, 0x5c, 0xac, 0x25, 0x89, 0x4d, 0xc5, 0x9c, 0x99, 0x15, 0x07, 0xcf, 0xba,
  0xc5, 0x9b, 0x15, 0x4d, 0x8d, 0x2a, 0x1e, 0x1f, 0xea, 0x2b, 0x2f, 0x64, 0xa9, 0x50, 0x3d, 0xab,
  0x50, 0x77, 0xe9, 0xc0, 0xac, 0x6d, 0x3f, 0xca, 0xcf, 0x71, 0x7d, 0x80, 0xa6, 0xfd, 0xff, 0xb5,
  0xbd, 0x6f, 0x24, 0x7b, 0x00, 0x99, 0x5d, 0xb1, 0x48, 0xb0, 0x28, 0x7f, 0x80, 0xec, 0xbf, 0x6f,
  0x6e, 0x39, 0x90, 0x42, 0xd9, 0x4e, 0x2e, 0x12, 0x66, 0xc8, 0xcf, 0x3b, 0x3f, 0x10, 0x7d, 0x79,
  0x00, 0xd3, 0x1f, 0x21, 0x93, 0x34, 0xd7, 0x19, 0x22, 0xa2, 0x08, 0x20, 0xb9, 0xb9, 0xef, 0x51,
  0x99, 0xde, 0xbf, 0xd4, 0x09, 0x75, 0xe9, 0x8a, 0xee, 0xfd, 0xe4, 0x4e, 0x30, 0x17, 0xdf, 0xce,
  0x11, 0xb2, 0x28, 0x35, 0xc2, 0x7c, 0x64, 0xeb, 0x91, 0x5f, 0x32, 0x0c, 0x6e, 0x00, 0xf9, 0x92,
  0x19, 0xdb, 0x8f, 0xab, 0xae, 0xd6, 0x12, 0xc4, 0x26, 0x62, 0xce, 0xcc, 0x0a, 0x03, 0xe7, 0xdd,
  0xe2, 0x4d, 0x8a, 0xa6, 0x46, 0x95, 0x0f, 0x8f, 0xf5, 0x15, 0x97, 0x32, 0xd4, 0x28, 0x1e, 0x55
  ];

//LFO
//
//  振幅変調(AM)は符号なし、周波数変調(PM)は符号あり
//
//          W=0 SAW ノコギリ波     W=1 SQUARE 方形波    W=2 TRIANGLE 三角波    W=3 NOISE ノイズ
//
//      255 ┌_            ┌    ┌───┐      ┌    X              X    ─────────
//          │  ─_        │    │      │      │      \          /
//  AM      │      ─_    │    │      │      │        \      /
//          │          ─_│    │      │      │          \  /
//        0 ┘…………………┘    ┘………└───┘    …………X…………    ─────────
//
//      128       _┐            ┌───┐      ┌        X                ─────────
//            _─  │            │      │      │      /  \
//  PM    0 ─………│………─    │………│………│    /………\………/    ………………………
//                  │  ─ ̄      │      │      │              \  /
//     -128         └ ̄          ┘      └───┘                X        ─────────
//          0              255    0              255    0              255    0              255
//
let opmLFOAMTable;  //int[256 * 4]
let opmLFOPMTable;  //int[256 * 4]

//テーブル
let opmTLTable;  //int[OPM_TL_TABLE_SIZE]
let opmSinTable;  //int[OPM_SIN_TABLE_SIZE]
let opmFreqTable;  //int[768 * 11]
let opmDT1FreqTable;  //int[256]
let opmNoiseTable;  //int[32]



//opmInit ()
//  OPMを初期化する
function opmInit () {

  //LFO
  opmLFOAMTable = new Int32Array (256 * 4);
  opmLFOPMTable = new Int32Array (256 * 4);
  for (let i = 0; i < 256; i++) {
    //W=0 SAW ノコギリ波
    opmLFOAMTable[i] = 255 - i;
    opmLFOPMTable[i] = i < 128 ? i : i - 255;
    //W=1 SQUARE 方形波
    opmLFOAMTable[256 + i] = i < 128 ? 255 : 0;
    opmLFOPMTable[256 + i] = i < 128 ? 128 : -128;
    //W=2 TRIANGLE 三角波
    opmLFOAMTable[512 + i] = i < 128 ? 255 - i * 2 : i * 2 - 256;
    opmLFOPMTable[512 + i] = i < 64 ? i * 2 : i < 128 ? 255 - i * 2 : i < 192 ? 256 - i * 2 : i * 2 - 511;
    //W=3 NOISE ノイズ
    opmLFOAMTable[768 + i] = OPM_LFO_NOISE_BASE[i];
    opmLFOPMTable[768 + i] = OPM_LFO_NOISE_BASE[i] - 128;
  }

  //テーブル
  opmTLTable = new Int32Array (OPM_TL_TABLE_SIZE);
  for (let x = 0; x < OPM_TL_BLOCK_SIZE; x += 2) {
    let t = ((trunc (pow (2.0, 16.0 + (x + 2) / -(OPM_TL_SIZE >> 1))) + 16) & -32) >> 3;
    opmTLTable[x] = t;
    opmTLTable[x + 1] = -t;
    for (let i = 1; i < 13; i++) {
      opmTLTable[x + OPM_TL_BLOCK_SIZE * i + 1] = -(opmTLTable[x + OPM_TL_BLOCK_SIZE * i] = t >> i);
    }
  }

  opmSinTable = new Int32Array (OPM_SIN_TABLE_SIZE);
  for (let i = 0; i < OPM_SIN_TABLE_SIZE; i++) {
    opmSinTable[i] = ((trunc ((-OPM_TL_SIZE >> 1) / LN2 *
                              log (abs (sin (PI / OPM_SIN_TABLE_SIZE * (i * 2 + 1))))
                              ) + 1) & ~1) +
      (i < OPM_SIN_TABLE_SIZE >> 1 ? 0 : 1);  //0..4275。最大値は0,OPM_SIN_TABLE_SIZE/2,OPM_SIN_TABLE_SIZE-1の3箇所
  }

  opmFreqTable = new Int32Array (768 * 11);
  for (let i = 0; i < 768; i++) {
    let t = OPM_PG_BASE_TABLE[i];
    opmFreqTable[768 * 1 + i] = t >> 2 << (    OPM_PHASE_SHIFT - 10);  //下位2ビットを削ってから左にシフトする
    opmFreqTable[768 * 2 + i] = t >> 1 << (    OPM_PHASE_SHIFT - 10);  //下位1ビットを削ってから左にシフトする
    opmFreqTable[768 * 3 + i] = t      << (    OPM_PHASE_SHIFT - 10);
    opmFreqTable[768 * 4 + i] = t      << (1 + OPM_PHASE_SHIFT - 10);
    opmFreqTable[768 * 5 + i] = t      << (2 + OPM_PHASE_SHIFT - 10);
    opmFreqTable[768 * 6 + i] = t      << (3 + OPM_PHASE_SHIFT - 10);
    opmFreqTable[768 * 7 + i] = t      << (4 + OPM_PHASE_SHIFT - 10);
    opmFreqTable[768 * 8 + i] = t      << (5 + OPM_PHASE_SHIFT - 10);
  }
  {
    let t = opmFreqTable[768 * 1 + 0];
    for (let i = 768 * 0; i < 768 * 1; i++) {
      opmFreqTable[i] = t;
    }
    t = opmFreqTable[768 * 9 - 1];
    for (let i = 768 * 9; i < 768 * 11; i++) {
      opmFreqTable[i] = t;
    }
  }

  opmDT1FreqTable = new Int32Array (256);
  for (let i = 0; i < 128; i++) {
    let t = OPM_DT1_BASE_TABLE[i] << (OPM_SIN_BITS + OPM_PHASE_SHIFT - 20);
    opmDT1FreqTable[i] = t;
    opmDT1FreqTable[128 + i] = -t;
  }

  opmNoiseTable = new Int32Array (32);
  for (let i = 0; i < 32; i++) {
    opmNoiseTable[i] = 2048 / (32 - i + ((i + 1) >> 5)) << 6;
  }

  //ポート
  opmPortArray = new Array (OPM_PORT_COUNT);
  for (let pn = 0; pn < OPM_PORT_COUNT; pn++) {  //ポート番号

    let port = opmPortArray[pn] = {

      op$PortNumber: pn,

      //レジスタ
      //op$Address: 0,  //int  アドレス

      //タイマ
      op$CLKA: 0,  //int  タイマAの周波数。0~1023
      op$CLKB: 0,  //int  タイマBの周波数。0~255
      op$IntervalA: 64 / OPM_OSC_FREQ * (1024 - 0),  //double  タイマAの周期。64/OPM_OSC_FREQ*(1024-opmCLKA)
      op$IntervalB: 1024 / OPM_OSC_FREQ * (256 - 0),  //double  タイマBの周期。1024/OPM_OSC_FREQ*(256-opmCLKB)
      op$ClockA: Infinity,  //double  タイマAがオーバーフローする時刻。isFinite(opmClockA)ならばタイマA動作中
      op$ClockB: Infinity,  //double  タイマBがオーバーフローする時刻。isFinite(opmClockB)ならばタイマA動作中
      op$ISTA: 0,  //int  OPM_ISTA=タイマAオーバーフロー
      op$ISTB: 0,  //int  OPM_ISTB=タイマBオーバーフロー。OPMIRQはopmISTAとopmISTBが両方0のとき1、どちらかが0でなければ0
      op$IRQENA: false,  //boolean  true=タイマAのオーバーフローをopmISTAに反映する
      op$IRQENB: false,  //boolean  true=タイマBのオーバーフローをopmISTBに反映する
      op$CSMOn: false,  //boolean  true=タイマAがオーバーフローしたら全スロットキーON
      op$BusyEndTime: 0,  //double  ビジーが終了する時刻。データレジスタに最後に書き込んだときのcommandTime+68/OPM_OSC_FREQ。ビジーフラグを変化させるだけで書き込めなくなるわけではない

      //EG
      op$EGCounter: 1,  //int  初期値は1。EGを更新するとき4ずつ増やす。bit0は常に1
      op$EGTimer: 3,  //int  初期値は3。1サンプルごとに1ずつ減らす。3回に1回、1→0のとき3に戻してEGを更新する

      //LFO
      op$LFOActive: true,  //boolean
      op$LFOWaveIndex: 0,  //int
      op$WAVE256: 0,  //int
      op$LFOAMValue: opmLFOAMTable[0],  //int
      op$LFOPMValue: opmLFOPMTable[0],  //int
      op$LFOCounterMinor: 1 << 18,  //int
      op$LFOPeriodMinor: 1 << 18,  //int
      op$LFOCounterMajor: 0,  //int
      op$LFOPeriodMajor: 16,  //int
      op$AMD: 0,  //int
      op$PMD: 0,  //int
      op$LFOAMOutput: 0,  //int
      op$LFOPMOutput: 0,  //int

      //NOISE
      op$NoiseRegister: 0,  //int
      op$NoisePhase: 0,  //int
      op$NoiseFrequency: opmNoiseTable[0],  //int

      //CSM
      op$CSMRequest: 0,  //int 0=何もしない,1=次のデータの直後でキーONする,-1=次のデータの直後でキーOFFする

      //ジョイント
      op$Joint1Box: new Int32Array ([0]),  //int[1]
      op$Joint2Box: new Int32Array ([0]),  //int[1]
      op$Joint3Box: new Int32Array ([0]),  //int[1]
      op$Joint4Box: new Int32Array ([0]),  //int[1]

      //スロット
      op$Slot: new Array (OPM_SLOT_COUNT),  //OPMSlot[OPM_SLOT_COUNT]

      //チャンネル
      op$Channel: new Array (OPM_CHANNEL_COUNT),  //OPMChannel[OPM_CHANNEL_COUNT]

      //その他
      op$CT1: 0,  //CT1 ADPCMの原発振周波数の設定。0=8MHz/8MHz,1=4MHz/16MHz。PEDECのCT1に入力
      op$CT2: 0  //CT2 FDCの強制レディ状態の設定。0=通常動作,1=強制レディ状態。UPD72065BのREADYにORで入力

      };  //port

    //スロット
    for (let sn = 0; sn < OPM_SLOT_COUNT; sn++) {  //スロット番号

      let slot = port.op$Slot[sn] = {

        sl$SlotNumber: sn,  //int

        sl$Port: port,
        sl$Channel: null,  //OPMChannel

        sl$Phase: 0,  //int
        sl$Freq: 0,  //int  周波数
        sl$Detune1Freq: 0,  //int
        sl$Multiply: 0,  //int  周波数の倍率の2倍。1,2~30
        sl$Detune1Page: 0,  //int  0..224
        sl$Detune2Depth: 0,  //int  大きいデチューン
        sl$AMSMask: 0,  //int  振幅変調のマスク。0=振幅変調しない,-1=振幅変調する
        sl$TotalLevel: 0,  //int  トータルレベル
        sl$Volume: OPM_TL_SIZE - 1,  //int
        sl$KeyStatus: 0,  //int  bit0:1=マニュアルでキーONされている,bit1:1=タイマーでキーONされている
        sl$KeyScale: 0,  //int  2..5。5-KS。AR,D1R,D2RのRATEはmin(63,2*R+KC>>(5-KS))、RRのRATEはmin(63,2+4*RR+KC>>(5-KS))

        sl$Stage: OPMStage_SILENCE,  //OPMStage  EGのステージ
        sl$AttackRate: 0,  //int  0..94。AR==0?0:32+2*AR。アタックレート
        sl$AttackShift: 0,  //int
        sl$Attack3: 0,  //int
        sl$Attack4: 0,  //int
        sl$DecayRate: 0,  //int  0..94。D1R==0?0:32+2*D1R。ファーストディケイレート
        sl$DecayShift: 0,  //int
        sl$Decay3: 0,  //int
        sl$Decay4: 0,  //int
        sl$DecayLevel: 0,  //int  (D1L<15?D1L:31+D1L)<<(OPM_TL_BITS-5)。ファーストディケイレベル
        sl$SustainRate: 0,  //int  0..94。D2R==0?0:32+2*D2R。セカンドディケイレート
        sl$SustainShift: 0,  //int
        sl$Sustain3: 0,  //int
        sl$Sustain4: 0,  //int
        sl$ReleaseRate: 0,  //int  34..94。32+2+4*RR。リリースレート
        sl$ReleaseShift: 0,  //int
        sl$Release3: 0,  //int
        sl$Release4: 0,  //int
        sl$TransitionMask: 1  //int  SILENCEのとき1,その他(4<<slXXXShift)-4。opmEGCounterのbit0は常に1なのでSILENCEのときはマスクしても0にならない

        };

      //slReset (slot);  //chResetにあるので省略

    }  //for sn

    //チャンネル
    for (let cn = 0; cn < OPM_CHANNEL_COUNT; cn++) {  //チャンネル番号

      //  スロットの通し番号の順序は小さい方からM1,M2,C1,C2であり、OPMドライバの音色データの順序M1,C1,M2,C2と異なる
      //  OPMドライバは音色データの順序を入れ替えてレジスタに設定している
      let cnlo = cn & 7;
      let cnhi = cn >> 3;
      let m1 = port.op$Slot[(cnhi << 5) +      cnlo];
      let m2 = port.op$Slot[(cnhi << 5) +  8 + cnlo];
      let c1 = port.op$Slot[(cnhi << 5) + 16 + cnlo];
      let c2 = port.op$Slot[(cnhi << 5) + 24 + cnlo];

      let channel = port.op$Channel[cn] = {

        ch$ChannelNumber: cn,  //int

        ch$Port: port,

        ch$M1: m1,  //OPMSlot  M1
        ch$C1: c1,  //OPMSlot  C1
        ch$M2: m2,  //OPMSlot  M2
        ch$C2: c2,  //OPMSlot  C2

        ch$KC: 0,  //int  0..127。キーコード
        ch$KF: 0,  //int  0..63。キーフラクション
        ch$KeyIndex: 0,  //int  キーコードとキーフラクションを合わせたインデックス。1オクターブは64*12=768で768の下駄履き

        ch$OutputBox: new Int32Array ([0]),  //int[1]  チャンネルの出力
        ch$fbOutputHandle: port.op$Joint1Box,  //int *  フィードバック回路の出力。前回の入力
        ch$fbInputValue: 0,  //int  フィードバック回路の前回の入力
        ch$fbPreviousValue: 0,  //int  フィードバック回路の前々回の入力
        ch$fbScale: 0,  //int  フィードバック回路のスケーリング。0=OFF,128=π/16,256=π/8,512=π/4,1024=π/2,2048=π,4096=2π,8192=4π
        ch$bfInputHandle: port.op$Joint2Box,  //int *  バッファの入力
        ch$bfInputValue: 0,  //int  バッファの値
        ch$bfOutputHandle: port.op$Joint3Box,  //int *  バッファの出力
        ch$LeftMask: 0,  //int  左側の出力のマスク。0=OFF,-1=ON
        ch$RightMask: 0,  //int  右側の出力のマスク。0=OFF,-1=ON
        ch$NoiseOn: false,  //boolean  true=C2がノイズスロットになる
        ch$c1InputHandle: port.op$Joint1Box,  //int *
        ch$c1OutputHandle: port.op$Joint2Box,  //int *
        ch$m2InputHandle: port.op$Joint3Box,  //int *
        ch$m2OutputHandle: port.op$Joint4Box,  //int *
        ch$c2InputHandle: port.op$Joint4Box,  //int *
        ch$FLCON: 0,  //int
        ch$SLOT: 0,  //int
        ch$SYNC: false,  //boolean
        ch$PAN: 0,  //int
        ch$AMS: 0,  //int  振幅変調
        ch$ShiftAMS: 0,  //int  振幅変調のスケーリング
        ch$PMS: 0,  //int  周波数変調
        ch$ShiftPMS: 0  //int  周波数変調のスケーリング

        };

      m1.sl$Channel = channel;
      m2.sl$Channel = channel;
      c1.sl$Channel = channel;
      c2.sl$Channel = channel;

      chReset (channel);

    }  //for cn

    //レジスタ
    opmSet (port, 0x1b, 0);
    opmSet (port, 0x18, 0);
    for (let cnhi = 0; cnhi < (OPM_CHANNEL_COUNT >> 3); cnhi++) {  //ch>>3
      for (let a = 0x20; a <= 0xff; a++) {
        opmSet (port, (cnhi << 8) + a, 0);
      }
    }

  }  //for pn

}  //opmInit

//slot.slReset (slot)
//  スロットを初期化する
function slReset (slot) {

  slot.sl$Phase = 0;
  slot.sl$Freq = 0;
  slot.sl$Detune1Freq = 0;
  slot.sl$Multiply = 0;
  slot.sl$Detune1Page = 0;
  slot.sl$Detune2Depth = 0;
  slot.sl$AMSMask = 0;
  slot.sl$TotalLevel = 0;
  slot.sl$Volume = OPM_TL_SIZE - 1;
  slot.sl$KeyStatus = 0;
  slot.sl$KeyScale = 0;

  slot.sl$Stage = OPMStage_SILENCE;
  slot.sl$AttackRate = 0;
  slot.sl$AttackShift = 0;
  slot.sl$Attack3 = 0;
  slot.sl$Attack4 = 0;
  slot.sl$DecayRate = 0;
  slot.sl$DecayShift = 0;
  slot.sl$Decay3 = 0;
  slot.sl$Decay4 = 0;
  slot.sl$DecayLevel = 0;
  slot.sl$SustainRate = 0;
  slot.sl$SustainShift = 0;
  slot.sl$Sustain3 = 0;
  slot.sl$Sustain4 = 0;
  slot.sl$ReleaseRate = 0;
  slot.sl$ReleaseShift = 0;
  slot.sl$Release3 = 0;
  slot.sl$Release4 = 0;
  slot.sl$TransitionMask = 1;

}

//channel.chReset (channel)
//  チャンネルを初期化する
function chReset (channel) {
  let port = channel.ch$Port;

  slReset (channel.ch$M1);
  slReset (channel.ch$C1);
  slReset (channel.ch$M2);
  slReset (channel.ch$C2);

  channel.ch$KC = 0;
  channel.ch$KF = 0;
  channel.ch$KeyIndex = 768;

  channel.ch$OutputBox[0] = 0;
  channel.ch$fbOutputHandle = port.op$Joint1Box;
  channel.ch$fbInputValue = 0;
  channel.ch$fbPreviousValue = 0;
  channel.ch$fbScale = 0;
  channel.ch$bfInputHandle = port.op$Joint2Box;
  channel.ch$bfInputValue = 0;
  channel.ch$bfOutputHandle = port.op$Joint3Box;
  channel.ch$LeftMask = 0;
  channel.ch$RightMask = 0;
  channel.ch$NoiseOn = false;
  //channel.ch$c1InputHandle = port.op$Joint1Box;
  channel.ch$c1OutputHandle = port.op$Joint2Box;
  //channel.ch$m2InputHandle = port.op$Joint3Box;
  channel.ch$m2OutputHandle = port.op$Joint4Box;
  channel.ch$c2InputHandle = port.op$Joint4Box;
  channel.ch$FLCON = 0;
  channel.ch$SLOT = 0;
  channel.ch$SYNC = true;
  channel.ch$PAN = 0;
  channel.ch$AMS = 0;
  channel.ch$ShiftAMS = 0;
  channel.ch$PMS = 0;
  channel.ch$ShiftPMS = 0;
}

//  レジスタ読み出し
function opmRead (port) {
  return (commandTime < port.op$BusyEndTime ? 0x80 : 0x00) | port.op$ISTB | port.op$ISTA;
}

//  レジスタ書き込み
//
//  アドレスレジスタへの書き込みではビジーフラグをセットしない
//    https://twitter.com/kamadox/status/980435716441124867
//  X68030はアドレスレジスタへの書き込みに限って約1.2μsのウェイトが追加されている
//    http://retropc.net/x68000/software/hardware/060turbo/060opmp/060opmp.htm
//
//  データレジスタへの書き込みでビジーフラグをセットする
//    データレジスタに書き込んでからビジーフラグがセットされるまでのタイムラグを再現することもできなくはないが、
//    実機をクロックアップすると動かなくなるソフトが実機と同じように動かなくなるだけのような気がするのでやめておく
//    https://twitter.com/kamadox/status/980436526713651200
//
function opmSet (port, a, d) {
  port.op$BusyEndTime = commandTime + 68 / OPM_OSC_FREQ;
  let cnhi = (a >> 8) & ((OPM_CHANNEL_COUNT >> 3) - 1);  //ch>>3
  a &= 0xff;
  d &= 0xff;
  if (a < 0x20) {
    switch (a) {
    case 0x01:
      port.op$LFOActive = (d & 2) == 0;
      if (!port.op$LFOActive) {
        port.op$LFOWaveIndex = 0;
        port.op$LFOAMValue = opmLFOAMTable[port.op$WAVE256];
        port.op$LFOPMValue = opmLFOPMTable[port.op$WAVE256];
      }
      break;
    case 0x08:
      chKeyOnOff (port.op$Channel[(cnhi << 3) + (d & 7)], d >> 3);
      break;
    case 0x0f:
      //!!! チャンネル数を増やすときノイズスロットになるのはチャンネル7のみでよいか
      port.op$Channel[7].ch$NoiseOn = d < 0;
      port.op$NoiseFrequency = opmNoiseTable[d & 31];
      break;
    case 0x10:  //CLKA1
      port.op$CLKA = d << 2 | (port.op$CLKA & 0x03);
      port.op$IntervalA = 64 / OPM_OSC_FREQ * (1024 - port.op$CLKA);
      if (isFinite (port.op$ClockA)) {  //タイマA動作中
        port.op$ClockA = commandTime + port.op$IntervalA;
        removeCommand ("opminta", port.op$PortNumber);
        addCommand ({
        t: port.op$ClockA,
        c: "opminta",
        p: port.op$PortNumber
        });
      }
      break;
    case 0x11:  //CLKA2
      port.op$CLKA = (port.op$CLKA & 0x3fc) | (d & 0x03);
      port.op$IntervalA = 64 / OPM_OSC_FREQ * (1024 - port.op$CLKA);
      if (isFinite (port.op$ClockA)) {  //タイマA動作中
        port.op$ClockA = commandTime + port.op$IntervalA;
        removeCommand ("opminta", port.op$PortNumber);
        addCommand ({
        t: port.op$ClockA,
        c: "opminta",
        p: port.op$PortNumber
        });
      }
      break;
    case 0x12:  //CLKB
      port.op$CLKB = d;
      port.op$IntervalB = 1024 / OPM_OSC_FREQ * (256 - port.op$CLKB);
      if (isFinite (port.op$ClockB)) {  //タイマB動作中
        port.op$ClockB = commandTime + port.op$IntervalB;
        removeCommand ("opmintb", port.op$PortNumber);
        addCommand ({
        t: port.op$ClockB,
        c: "opmintb",
        p: port.op$PortNumber
        });
      }
      break;
    case 0x14:  //CSM,F RESET,IRQEN,LOAD
      if ((d & OPM_LOADA) != 0) {  //タイマA始動
        if (!isFinite (port.op$ClockA)) {  //タイマA停止中。LOADA 0→1
          if ((port.op$ISTA | port.op$ISTB) != 0) {
            //MC68901.mfpOpmirqFall ();
            opmpcmProcessor.port.postMessage ({
            t: commandTime,
            c: "opmirq0",
            p: port.op$PortNumber
            });
          } else {
            //MC68901.mfpOpmirqRise ();
            opmpcmProcessor.port.postMessage ({
            t: commandTime,
            c: "opmirq1",
            p: port.op$PortNumber
            });
          }
          port.op$ClockA = commandTime + port.op$IntervalA;
          removeCommand ("opminta", port.op$PortNumber);
          addCommand ({
          t: port.op$ClockA,
          c: "opminta",
          p: port.op$PortNumber
          });
        }
      } else {  //タイマA停止
        if (isFinite (port.op$ClockA)) {  //タイマA動作中。LOADA 1→0
          if ((port.op$ISTA | port.op$ISTB) != 0) {
            //MC68901.mfpOpmirqFall ();
            opmpcmProcessor.port.postMessage ({
            t: commandTime,
            c: "opmirq0",
            p: port.op$PortNumber
            });
          } else {
            //MC68901.mfpOpmirqRise ();
            opmpcmProcessor.port.postMessage ({
            t: commandTime,
            c: "opmirq1",
            p: port.op$PortNumber
            });
          }
          port.op$ClockA = Infinity;
          removeCommand ("opminta", port.op$PortNumber);
          addCommand ({
          t: port.op$ClockA,
          c: "opminta",
          p: port.op$PortNumber
          });
        }
      }
      if ((d & OPM_LOADB) != 0) {  //タイマB始動
        if (!isFinite (port.op$ClockB)) {  //タイマB停止中。LOADB 0→1
          if ((port.op$ISTA | port.op$ISTB) != 0) {
            //MC68901.mfpOpmirqFall ();
            opmpcmProcessor.port.postMessage ({
            t: commandTime,
            c: "opmirq0",
            p: port.op$PortNumber
            });
          } else {
            //MC68901.mfpOpmirqRise ();
            opmpcmProcessor.port.postMessage ({
            t: commandTime,
            c: "opmirq1",
            p: port.op$PortNumber
            });
          }
          port.op$ClockB = commandTime + port.op$IntervalB;
          removeCommand ("opmintb", port.op$PortNumber);
          addCommand ({
          t: port.op$ClockB,
          c: "opmintb",
          p: port.op$PortNumber
          });
        }
      } else {  //タイマB停止
        if (isFinite (port.op$ClockB)) {  //タイマB動作中。LOADB 1→0
          if ((port.op$ISTA | port.op$ISTB) != 0) {
            //MC68901.mfpOpmirqFall ();
            opmpcmProcessor.port.postMessage ({
            t: commandTime,
            c: "opmirq0",
            p: port.op$PortNumber
            });
          } else {
            //MC68901.mfpOpmirqRise ();
            opmpcmProcessor.port.postMessage ({
            t: commandTime,
            c: "opmirq1",
            p: port.op$PortNumber
            });
          }
          port.op$ClockB = Infinity;
          removeCommand ("opmintb", port.op$PortNumber);
          addCommand ({
          t: port.op$ClockB,
          c: "opmintb",
          p: port.op$PortNumber
          });
        }
      }
      port.op$IRQENA = (d & OPM_IRQENA) != 0;
      port.op$IRQENB = (d & OPM_IRQENB) != 0;
      if ((d & OPM_RESETA) != 0) {  //タイマAフラグレジスタリセット
        port.op$ISTA = 0;
        if (port.op$ISTB != 0) {
          //MC68901.mfpOpmirqFall ();
          opmpcmProcessor.port.postMessage ({
          t: commandTime,
          c: "opmirq0",
          p: port.op$PortNumber
          });
        } else {
          //MC68901.mfpOpmirqRise ();
          opmpcmProcessor.port.postMessage ({
          t: commandTime,
          c: "opmirq1",
          p: port.op$PortNumber
          });
        }
      }
      if ((d & OPM_RESETB) != 0) {  //タイマBフラグレジスタリセット
        port.op$ISTB = 0;
        if (port.op$ISTA != 0) {
          //MC68901.mfpOpmirqFall ();
          opmpcmProcessor.port.postMessage ({
          t: commandTime,
          c: "opmirq0",
          p: port.op$PortNumber
          });
        } else {
          //MC68901.mfpOpmirqRise ();
          opmpcmProcessor.port.postMessage ({
          t: commandTime,
          c: "opmirq1",
          p: port.op$PortNumber
          });
        }
      }
      port.op$CSMOn = d < 0;
      break;
    case 0x18:
      port.op$LFOPeriodMajor = 16 + (d & 15);
      port.op$LFOCounterMinor = port.op$LFOPeriodMinor = 1 << (18 - (d >> 4));
      break;
    case 0x19:
      if ((d & 0x80) == 0) {
        port.op$AMD = d & 127;
      } else {
        port.op$PMD = d & 127;
      }
      break;
    case 0x1b:
      port.op$WAVE256 = (d & 3) << 8;
      port.op$LFOAMValue = opmLFOAMTable[port.op$WAVE256 + port.op$LFOWaveIndex];
      port.op$LFOPMValue = opmLFOPMTable[port.op$WAVE256 + port.op$LFOWaveIndex];
      let ct1 = d >> 7 & 1;
      if (port.op$CT1 != ct1) {
        port.op$CT1 = ct1;
        //ADPCM.pcmOscillator = ct1;
        //ADPCM.pcmUpdateRepeatInterval ();
        opmpcmProcessor.port.postMessage ({
        t: commandTime,
        c: "opmct1" + ct1,
        p: port.op$PortNumber
        });
      }
      let ct2 = d >> 6 & 1;
      if (port.op$CT2 != ct2) {
        port.op$CT2 = ct2;
        //FDC.fdcSetEnforcedReady (ct2);
        opmpcmProcessor.port.postMessage ({
        t: commandTime,
        c: "opmct2" + ct2,
        p: port.op$PortNumber
        });
      }
      break;
    }
  } else if (a < 0x40) {
    let channel = port.op$Channel[(cnhi << 3) + (a & 7)];
    switch (a >> 3) {
    case 0x20 >> 3:
      chSetFLCON (channel, d);
      chSetPAN (channel, d >> 6);
      break;
    case 0x28 >> 3:
      chSetKC (channel, d);
      break;
    case 0x30 >> 3:
      chSetKF (channel, d >> 2);
      break;
    case 0x38 >> 3:
      chSetAMS (channel, d);
      chSetPMS (channel, d >> 4);
      break;
    }
  } else {
    let slot = port.op$Slot[(cnhi << 5) + (a & 31)];
    switch (a >> 5) {
    case 0x40 >> 5:
      slSetMUL (slot, d);
      slSetDT1 (slot, d >> 4);
      break;
    case 0x60 >> 5:
      slSetTL (slot, d);
      break;
    case 0x80 >> 5:
      slSetAR (slot, d);
      slSetKS (slot, d >> 6);
      break;
    case 0xa0 >> 5:
      slSetD1R (slot, d);
      slSetAMSEN (slot, d >> 7);
      break;
    case 0xc0 >> 5:
      slSetD2R (slot, d);
      slSetDT2 (slot, d >> 6);
      break;
    case 0xe0 >> 5:
      slSetRR (slot, d);
      slSetD1L (slot, d >> 4);
      break;
    }
  }
}

//  OPMタイマA割り込み
function opmIntA (port) {
  //次の割り込み時刻を設定する
  port.op$ClockA += port.op$IntervalA;
  //removeCommand ("opminta", command.p);
  addCommand ({
  t: port.op$ClockA,
  c: "opminta",
  p: port.op$PortNumber
  });
  //設定に応じてMFPに割り込み要求を出す
  if (port.op$IRQENA) {
    port.op$ISTA = OPM_ISTA;
    //MC68901.mfpOpmirqFall ();
    opmpcmProcessor.port.postMessage ({
    t: commandTime,
    c: "opmirq0",
    p: port.op$PortNumber
    });
  }
  if (port.op$CSMOn) {  //全スロットキーON
    port.op$CSMRequest = 1;
  }
}

//  OPMタイマB割り込み
function opmIntB (port) {
  //次の割り込み時刻を設定する
  port.op$ClockB += port.op$IntervalB;
  //removeCommand ("opmintb", command.p);
  addCommand ({
  t: port.op$ClockB,
  c: "opmintb",
  p: port.op$PortNumber
  });
  //設定に応じてMFPに割り込み要求を出す
  if (port.op$IRQENB) {
    port.op$ISTB = OPM_ISTB;
    //MC68901.mfpOpmirqFall ();
    opmpcmProcessor.port.postMessage ({
    t: commandTime,
    c: "opmirq0",
    p: port.op$PortNumber
    });
  }
  if (port.op$CSMOn) {  //全スロットキーON
    port.op$CSMRequest = 1;
  }
}

//channel.chKeyOnOff (channel, mask)
//  チャンネルの各スロットをキーONまたはキーOFFする
function chKeyOnOff (channel, mask) {
  let port = channel.ch$Port;
  if (mask != 0) {
    channel.ch$SLOT = mask;
    channel.ch$SYNC = port.op$LFOWaveIndex == 0;
  }
  //  Inside X68000にKONのスロットマスクのビットの順序が上位からC2|C1|M2|M1と書かれているが、C2|M2|C1|M1の誤り
  //  Human68k ver 3.0ユーザーズマニュアルのスロットマスクの説明ではC2|M2|C1|M1となっており、こちらが正しい
  if ((mask & 0x01) != 0) {  //0b0001
    slKeyOn (channel.ch$M1, 1);
  } else {
    slKeyOff (channel.ch$M1, ~1);
  }
  if ((mask & 0x02) != 0) {  //0b0010
    slKeyOn (channel.ch$C1, 1);
  } else {
    slKeyOff (channel.ch$C1, ~1);
  }
  if ((mask & 0x04) != 0) {  //0b0100
    slKeyOn (channel.ch$M2, 1);
  } else {
    slKeyOff (channel.ch$M2, ~1);
  }
  if ((mask & 0x08) != 0) {  //0b1000
    slKeyOn (channel.ch$C2, 1);
  } else {
    slKeyOff (channel.ch$C2, ~1);
  }
}

//channel.chSetFLCON (channel, v)
//  チャンネルのフィードバックレベルとコネクションを設定する
function chSetFLCON (channel, v) {
  let port = channel.ch$Port;
  v &= 63;
  channel.ch$FLCON = v;
  switch (v & 7) {
    //  M1                          M2              モジュレータ
    //              C1                      C2      キャリア
    //      FB                                      フィードバック
    //                      BF                      バッファ
    //          ①      ②      ③      ④          ジョイント
    //                                          ◯  アウトプット
    //  ①→C1と③→M2はすべてのコネクションで共通なのでコンストラクタで接続する
    //  port.op$Joint1Box,port.op$Joint3Boxよりもch$c1InputHandle,ch$m2InputHandleの方がアクセスが速いのでch$c1InputHandle,ch$m2InputHandleを廃止することはしない
    //  バッファを使うコネクションからバッファを使わないコネクションに切り替えた直後にバッファからゴミが出てくる可能性があるので、
    //  バッファを使わないときはバッファの出力を空いているジョイントに繋いでおく
  case 0:
    //  ┌─┐
    //  ↓  │
    //  M1→FB→①→C1→②→BF→③→M2→④→C2→◯
    channel.ch$fbOutputHandle = port.op$Joint1Box;
    channel.ch$c1OutputHandle = channel.ch$bfInputHandle = port.op$Joint2Box;
    channel.ch$bfOutputHandle = port.op$Joint3Box;
    channel.ch$m2OutputHandle = channel.ch$c2InputHandle = port.op$Joint4Box;
    break;
  case 1:
    //  ┌─┐  ①→C1─┐
    //  ↓  │          ↓
    //  M1→FB────→②→BF→③→M2→④→C2→◯
    channel.ch$fbOutputHandle = channel.ch$c1OutputHandle = channel.ch$bfInputHandle = port.op$Joint2Box;
    channel.ch$bfOutputHandle = port.op$Joint3Box;
    channel.ch$m2OutputHandle = channel.ch$c2InputHandle = port.op$Joint4Box;
    break;
  case 2:
    //  ┌─┐  ①→C1→②→BF→③→M2─┐
    //  ↓  │                          ↓
    //  M1→FB────────────→④→C2→◯
    channel.ch$c1OutputHandle = channel.ch$bfInputHandle = port.op$Joint2Box;
    channel.ch$bfOutputHandle = port.op$Joint3Box;
    channel.ch$fbOutputHandle = channel.ch$m2OutputHandle = channel.ch$c2InputHandle = port.op$Joint4Box;
    break;
  case 3:
    //  ┌─┐                  ③→M2─┐
    //  ↓  │                          ↓
    //  M1→FB→①→C1→②→BF────→④→C2→◯
    channel.ch$fbOutputHandle = port.op$Joint1Box;
    channel.ch$c1OutputHandle = channel.ch$bfInputHandle = port.op$Joint2Box;
    channel.ch$bfOutputHandle = channel.ch$m2OutputHandle = channel.ch$c2InputHandle = port.op$Joint4Box;
    break;
  case 4:
    //  ┌─┐                  ③→M2→④→C2─┐
    //  ↓  │                                  ↓
    //  M1→FB→①→C1────────────→◯
    channel.ch$fbOutputHandle = port.op$Joint1Box;
    channel.ch$bfOutputHandle = port.op$Joint2Box;  //ゴミが出てくる可能性があるので空いているジョイントに繋いでおく
    channel.ch$m2OutputHandle = channel.ch$c2InputHandle = port.op$Joint4Box;
    channel.ch$c1OutputHandle = channel.ch$OutputBox;
    break;
  case 5:
    //  ┌─┐  ┌────→BF→③→M2─────┐
    //  ↓  │  │                              ↓
    //  M1→FB→①→C1────────────→◯
    //          │                              ↑
    //          └────────────→C2─┘
    channel.ch$fbOutputHandle = channel.ch$bfInputHandle = channel.ch$c2InputHandle = port.op$Joint1Box;
    channel.ch$bfOutputHandle = port.op$Joint3Box;
    channel.ch$c1OutputHandle = channel.ch$m2OutputHandle = channel.ch$OutputBox;
    break;
  case 6:
    //  ┌─┐                  ③→M2─────┐
    //  ↓  │                                  ↓
    //  M1→FB→①→C1────────────→◯
    //                                          ↑
←Previous | 1 2 3 4 | Next→