Index: build/vs2017win7/OpenMPT-ANSI.vcxproj =================================================================== --- build/vs2017win7/OpenMPT-ANSI.vcxproj (revision 25320) +++ build/vs2017win7/OpenMPT-ANSI.vcxproj (working copy) @@ -898,6 +898,9 @@ + + + @@ -1363,6 +1366,8 @@ + + @@ -1370,7 +1375,6 @@ - Index: build/vs2017win7/OpenMPT-ANSI.vcxproj.filters =================================================================== --- build/vs2017win7/OpenMPT-ANSI.vcxproj.filters (revision 25320) +++ build/vs2017win7/OpenMPT-ANSI.vcxproj.filters (working copy) @@ -711,6 +711,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2102,6 +2111,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2123,9 +2138,6 @@ mptrack - - mptrack - mptrack Index: build/vs2017win7/OpenMPT-UTF8.vcxproj =================================================================== --- build/vs2017win7/OpenMPT-UTF8.vcxproj (revision 25320) +++ build/vs2017win7/OpenMPT-UTF8.vcxproj (working copy) @@ -898,6 +898,9 @@ + + + @@ -1363,6 +1366,8 @@ + + @@ -1370,7 +1375,6 @@ - Index: build/vs2017win7/OpenMPT-UTF8.vcxproj.filters =================================================================== --- build/vs2017win7/OpenMPT-UTF8.vcxproj.filters (revision 25320) +++ build/vs2017win7/OpenMPT-UTF8.vcxproj.filters (working copy) @@ -711,6 +711,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2102,6 +2111,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2123,9 +2138,6 @@ mptrack - - mptrack - mptrack Index: build/vs2017win7/OpenMPT.vcxproj =================================================================== --- build/vs2017win7/OpenMPT.vcxproj (revision 25320) +++ build/vs2017win7/OpenMPT.vcxproj (working copy) @@ -898,6 +898,9 @@ + + + @@ -1363,6 +1366,8 @@ + + @@ -1370,7 +1375,6 @@ - Index: build/vs2017win7/OpenMPT.vcxproj.filters =================================================================== --- build/vs2017win7/OpenMPT.vcxproj.filters (revision 25320) +++ build/vs2017win7/OpenMPT.vcxproj.filters (working copy) @@ -711,6 +711,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2102,6 +2111,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2123,9 +2138,6 @@ mptrack - - mptrack - mptrack Index: build/vs2017winxp/OpenMPT-ANSI.vcxproj =================================================================== --- build/vs2017winxp/OpenMPT-ANSI.vcxproj (revision 25320) +++ build/vs2017winxp/OpenMPT-ANSI.vcxproj (working copy) @@ -547,6 +547,9 @@ + + + @@ -1012,6 +1015,8 @@ + + @@ -1019,7 +1024,6 @@ - Index: build/vs2017winxp/OpenMPT-ANSI.vcxproj.filters =================================================================== --- build/vs2017winxp/OpenMPT-ANSI.vcxproj.filters (revision 25320) +++ build/vs2017winxp/OpenMPT-ANSI.vcxproj.filters (working copy) @@ -711,6 +711,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2102,6 +2111,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2123,9 +2138,6 @@ mptrack - - mptrack - mptrack Index: build/vs2017winxp/OpenMPT-UTF8.vcxproj =================================================================== --- build/vs2017winxp/OpenMPT-UTF8.vcxproj (revision 25320) +++ build/vs2017winxp/OpenMPT-UTF8.vcxproj (working copy) @@ -547,6 +547,9 @@ + + + @@ -1012,6 +1015,8 @@ + + @@ -1019,7 +1024,6 @@ - Index: build/vs2017winxp/OpenMPT-UTF8.vcxproj.filters =================================================================== --- build/vs2017winxp/OpenMPT-UTF8.vcxproj.filters (revision 25320) +++ build/vs2017winxp/OpenMPT-UTF8.vcxproj.filters (working copy) @@ -711,6 +711,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2102,6 +2111,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2123,9 +2138,6 @@ mptrack - - mptrack - mptrack Index: build/vs2017winxp/OpenMPT.vcxproj =================================================================== --- build/vs2017winxp/OpenMPT.vcxproj (revision 25320) +++ build/vs2017winxp/OpenMPT.vcxproj (working copy) @@ -547,6 +547,9 @@ + + + @@ -1012,6 +1015,8 @@ + + @@ -1019,7 +1024,6 @@ - Index: build/vs2017winxp/OpenMPT.vcxproj.filters =================================================================== --- build/vs2017winxp/OpenMPT.vcxproj.filters (revision 25320) +++ build/vs2017winxp/OpenMPT.vcxproj.filters (working copy) @@ -711,6 +711,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2102,6 +2111,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2123,9 +2138,6 @@ mptrack - - mptrack - mptrack Index: build/vs2017winxpansi/OpenMPT-ANSI.vcxproj =================================================================== --- build/vs2017winxpansi/OpenMPT-ANSI.vcxproj (revision 25320) +++ build/vs2017winxpansi/OpenMPT-ANSI.vcxproj (working copy) @@ -547,6 +547,9 @@ + + + @@ -1012,6 +1015,8 @@ + + @@ -1019,7 +1024,6 @@ - Index: build/vs2017winxpansi/OpenMPT-ANSI.vcxproj.filters =================================================================== --- build/vs2017winxpansi/OpenMPT-ANSI.vcxproj.filters (revision 25320) +++ build/vs2017winxpansi/OpenMPT-ANSI.vcxproj.filters (working copy) @@ -711,6 +711,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2102,6 +2111,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2123,9 +2138,6 @@ mptrack - - mptrack - mptrack Index: build/vs2017winxpansi/OpenMPT-UTF8.vcxproj =================================================================== --- build/vs2017winxpansi/OpenMPT-UTF8.vcxproj (revision 25320) +++ build/vs2017winxpansi/OpenMPT-UTF8.vcxproj (working copy) @@ -547,6 +547,9 @@ + + + @@ -1012,6 +1015,8 @@ + + @@ -1019,7 +1024,6 @@ - Index: build/vs2017winxpansi/OpenMPT-UTF8.vcxproj.filters =================================================================== --- build/vs2017winxpansi/OpenMPT-UTF8.vcxproj.filters (revision 25320) +++ build/vs2017winxpansi/OpenMPT-UTF8.vcxproj.filters (working copy) @@ -711,6 +711,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2102,6 +2111,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2123,9 +2138,6 @@ mptrack - - mptrack - mptrack Index: build/vs2017winxpansi/OpenMPT.vcxproj =================================================================== --- build/vs2017winxpansi/OpenMPT.vcxproj (revision 25320) +++ build/vs2017winxpansi/OpenMPT.vcxproj (working copy) @@ -547,6 +547,9 @@ + + + @@ -1012,6 +1015,8 @@ + + @@ -1019,7 +1024,6 @@ - Index: build/vs2017winxpansi/OpenMPT.vcxproj.filters =================================================================== --- build/vs2017winxpansi/OpenMPT.vcxproj.filters (revision 25320) +++ build/vs2017winxpansi/OpenMPT.vcxproj.filters (working copy) @@ -711,6 +711,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2102,6 +2111,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2123,9 +2138,6 @@ mptrack - - mptrack - mptrack Index: build/vs2017winxpx64/OpenMPT-ANSI.vcxproj =================================================================== --- build/vs2017winxpx64/OpenMPT-ANSI.vcxproj (revision 25320) +++ build/vs2017winxpx64/OpenMPT-ANSI.vcxproj (working copy) @@ -934,6 +934,9 @@ + + + @@ -1399,6 +1402,8 @@ + + @@ -1406,7 +1411,6 @@ - Index: build/vs2017winxpx64/OpenMPT-ANSI.vcxproj.filters =================================================================== --- build/vs2017winxpx64/OpenMPT-ANSI.vcxproj.filters (revision 25320) +++ build/vs2017winxpx64/OpenMPT-ANSI.vcxproj.filters (working copy) @@ -711,6 +711,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2102,6 +2111,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2123,9 +2138,6 @@ mptrack - - mptrack - mptrack Index: build/vs2017winxpx64/OpenMPT-UTF8.vcxproj =================================================================== --- build/vs2017winxpx64/OpenMPT-UTF8.vcxproj (revision 25320) +++ build/vs2017winxpx64/OpenMPT-UTF8.vcxproj (working copy) @@ -934,6 +934,9 @@ + + + @@ -1399,6 +1402,8 @@ + + @@ -1406,7 +1411,6 @@ - Index: build/vs2017winxpx64/OpenMPT-UTF8.vcxproj.filters =================================================================== --- build/vs2017winxpx64/OpenMPT-UTF8.vcxproj.filters (revision 25320) +++ build/vs2017winxpx64/OpenMPT-UTF8.vcxproj.filters (working copy) @@ -711,6 +711,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2102,6 +2111,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2123,9 +2138,6 @@ mptrack - - mptrack - mptrack Index: build/vs2017winxpx64/OpenMPT.vcxproj =================================================================== --- build/vs2017winxpx64/OpenMPT.vcxproj (revision 25320) +++ build/vs2017winxpx64/OpenMPT.vcxproj (working copy) @@ -934,6 +934,9 @@ + + + @@ -1399,6 +1402,8 @@ + + @@ -1406,7 +1411,6 @@ - Index: build/vs2017winxpx64/OpenMPT.vcxproj.filters =================================================================== --- build/vs2017winxpx64/OpenMPT.vcxproj.filters (revision 25320) +++ build/vs2017winxpx64/OpenMPT.vcxproj.filters (working copy) @@ -711,6 +711,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2102,6 +2111,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2123,9 +2138,6 @@ mptrack - - mptrack - mptrack Index: build/vs2019win7/OpenMPT-ANSI.vcxproj =================================================================== --- build/vs2019win7/OpenMPT-ANSI.vcxproj (revision 25320) +++ build/vs2019win7/OpenMPT-ANSI.vcxproj (working copy) @@ -955,6 +955,9 @@ + + + @@ -1420,6 +1423,8 @@ + + @@ -1427,7 +1432,6 @@ - Index: build/vs2019win7/OpenMPT-ANSI.vcxproj.filters =================================================================== --- build/vs2019win7/OpenMPT-ANSI.vcxproj.filters (revision 25320) +++ build/vs2019win7/OpenMPT-ANSI.vcxproj.filters (working copy) @@ -750,6 +750,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2141,6 +2150,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2162,9 +2177,6 @@ mptrack - - mptrack - mptrack Index: build/vs2019win7/OpenMPT-UTF8.vcxproj =================================================================== --- build/vs2019win7/OpenMPT-UTF8.vcxproj (revision 25320) +++ build/vs2019win7/OpenMPT-UTF8.vcxproj (working copy) @@ -955,6 +955,9 @@ + + + @@ -1420,6 +1423,8 @@ + + @@ -1427,7 +1432,6 @@ - Index: build/vs2019win7/OpenMPT-UTF8.vcxproj.filters =================================================================== --- build/vs2019win7/OpenMPT-UTF8.vcxproj.filters (revision 25320) +++ build/vs2019win7/OpenMPT-UTF8.vcxproj.filters (working copy) @@ -750,6 +750,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2141,6 +2150,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2162,9 +2177,6 @@ mptrack - - mptrack - mptrack Index: build/vs2019win7/OpenMPT.vcxproj =================================================================== --- build/vs2019win7/OpenMPT.vcxproj (revision 25320) +++ build/vs2019win7/OpenMPT.vcxproj (working copy) @@ -955,6 +955,9 @@ + + + @@ -1420,6 +1423,8 @@ + + @@ -1427,7 +1432,6 @@ - Index: build/vs2019win7/OpenMPT.vcxproj.filters =================================================================== --- build/vs2019win7/OpenMPT.vcxproj.filters (revision 25320) +++ build/vs2019win7/OpenMPT.vcxproj.filters (working copy) @@ -750,6 +750,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2141,6 +2150,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2162,9 +2177,6 @@ mptrack - - mptrack - mptrack Index: build/vs2022win10/OpenMPT-ANSI.vcxproj =================================================================== --- build/vs2022win10/OpenMPT-ANSI.vcxproj (revision 25320) +++ build/vs2022win10/OpenMPT-ANSI.vcxproj (working copy) @@ -1767,6 +1767,9 @@ + + + @@ -2232,6 +2235,8 @@ + + @@ -2239,7 +2244,6 @@ - Index: build/vs2022win10/OpenMPT-ANSI.vcxproj.filters =================================================================== --- build/vs2022win10/OpenMPT-ANSI.vcxproj.filters (revision 25320) +++ build/vs2022win10/OpenMPT-ANSI.vcxproj.filters (working copy) @@ -750,6 +750,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2141,6 +2150,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2162,9 +2177,6 @@ mptrack - - mptrack - mptrack Index: build/vs2022win10/OpenMPT-UTF8.vcxproj =================================================================== --- build/vs2022win10/OpenMPT-UTF8.vcxproj (revision 25320) +++ build/vs2022win10/OpenMPT-UTF8.vcxproj (working copy) @@ -1767,6 +1767,9 @@ + + + @@ -2232,6 +2235,8 @@ + + @@ -2239,7 +2244,6 @@ - Index: build/vs2022win10/OpenMPT-UTF8.vcxproj.filters =================================================================== --- build/vs2022win10/OpenMPT-UTF8.vcxproj.filters (revision 25320) +++ build/vs2022win10/OpenMPT-UTF8.vcxproj.filters (working copy) @@ -750,6 +750,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2141,6 +2150,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2162,9 +2177,6 @@ mptrack - - mptrack - mptrack Index: build/vs2022win10/OpenMPT.vcxproj =================================================================== --- build/vs2022win10/OpenMPT.vcxproj (revision 25320) +++ build/vs2022win10/OpenMPT.vcxproj (working copy) @@ -1767,6 +1767,9 @@ + + + @@ -2232,6 +2235,8 @@ + + @@ -2239,7 +2244,6 @@ - Index: build/vs2022win10/OpenMPT.vcxproj.filters =================================================================== --- build/vs2022win10/OpenMPT.vcxproj.filters (revision 25320) +++ build/vs2022win10/OpenMPT.vcxproj.filters (working copy) @@ -750,6 +750,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2141,6 +2150,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2162,9 +2177,6 @@ mptrack - - mptrack - mptrack Index: build/vs2022win11/OpenMPT-ANSI.vcxproj =================================================================== --- build/vs2022win11/OpenMPT-ANSI.vcxproj (revision 25320) +++ build/vs2022win11/OpenMPT-ANSI.vcxproj (working copy) @@ -1769,6 +1769,9 @@ + + + @@ -2234,6 +2237,8 @@ + + @@ -2241,7 +2246,6 @@ - Index: build/vs2022win11/OpenMPT-ANSI.vcxproj.filters =================================================================== --- build/vs2022win11/OpenMPT-ANSI.vcxproj.filters (revision 25320) +++ build/vs2022win11/OpenMPT-ANSI.vcxproj.filters (working copy) @@ -750,6 +750,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2141,6 +2150,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2162,9 +2177,6 @@ mptrack - - mptrack - mptrack Index: build/vs2022win11/OpenMPT-UTF8.vcxproj =================================================================== --- build/vs2022win11/OpenMPT-UTF8.vcxproj (revision 25320) +++ build/vs2022win11/OpenMPT-UTF8.vcxproj (working copy) @@ -1769,6 +1769,9 @@ + + + @@ -2234,6 +2237,8 @@ + + @@ -2241,7 +2246,6 @@ - Index: build/vs2022win11/OpenMPT-UTF8.vcxproj.filters =================================================================== --- build/vs2022win11/OpenMPT-UTF8.vcxproj.filters (revision 25320) +++ build/vs2022win11/OpenMPT-UTF8.vcxproj.filters (working copy) @@ -750,6 +750,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2141,6 +2150,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2162,9 +2177,6 @@ mptrack - - mptrack - mptrack Index: build/vs2022win11/OpenMPT.vcxproj =================================================================== --- build/vs2022win11/OpenMPT.vcxproj (revision 25320) +++ build/vs2022win11/OpenMPT.vcxproj (working copy) @@ -1769,6 +1769,9 @@ + + + @@ -2234,6 +2237,8 @@ + + @@ -2241,7 +2246,6 @@ - Index: build/vs2022win11/OpenMPT.vcxproj.filters =================================================================== --- build/vs2022win11/OpenMPT.vcxproj.filters (revision 25320) +++ build/vs2022win11/OpenMPT.vcxproj.filters (working copy) @@ -750,6 +750,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2141,6 +2150,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2162,9 +2177,6 @@ mptrack - - mptrack - mptrack Index: build/vs2022win11clang/OpenMPT-ANSI.vcxproj =================================================================== --- build/vs2022win11clang/OpenMPT-ANSI.vcxproj (revision 25320) +++ build/vs2022win11clang/OpenMPT-ANSI.vcxproj (working copy) @@ -1323,6 +1323,9 @@ + + + @@ -1788,6 +1791,8 @@ + + @@ -1795,7 +1800,6 @@ - Index: build/vs2022win11clang/OpenMPT-ANSI.vcxproj.filters =================================================================== --- build/vs2022win11clang/OpenMPT-ANSI.vcxproj.filters (revision 25320) +++ build/vs2022win11clang/OpenMPT-ANSI.vcxproj.filters (working copy) @@ -711,6 +711,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2102,6 +2111,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2123,9 +2138,6 @@ mptrack - - mptrack - mptrack Index: build/vs2022win11clang/OpenMPT-UTF8.vcxproj =================================================================== --- build/vs2022win11clang/OpenMPT-UTF8.vcxproj (revision 25320) +++ build/vs2022win11clang/OpenMPT-UTF8.vcxproj (working copy) @@ -1323,6 +1323,9 @@ + + + @@ -1788,6 +1791,8 @@ + + @@ -1795,7 +1800,6 @@ - Index: build/vs2022win11clang/OpenMPT-UTF8.vcxproj.filters =================================================================== --- build/vs2022win11clang/OpenMPT-UTF8.vcxproj.filters (revision 25320) +++ build/vs2022win11clang/OpenMPT-UTF8.vcxproj.filters (working copy) @@ -711,6 +711,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2102,6 +2111,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2123,9 +2138,6 @@ mptrack - - mptrack - mptrack Index: build/vs2022win11clang/OpenMPT.vcxproj =================================================================== --- build/vs2022win11clang/OpenMPT.vcxproj (revision 25320) +++ build/vs2022win11clang/OpenMPT.vcxproj (working copy) @@ -1323,6 +1323,9 @@ + + + @@ -1788,6 +1791,8 @@ + + @@ -1795,7 +1800,6 @@ - Index: build/vs2022win11clang/OpenMPT.vcxproj.filters =================================================================== --- build/vs2022win11clang/OpenMPT.vcxproj.filters (revision 25320) +++ build/vs2022win11clang/OpenMPT.vcxproj.filters (working copy) @@ -711,6 +711,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2102,6 +2111,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2123,9 +2138,6 @@ mptrack - - mptrack - mptrack Index: build/vs2022win7/OpenMPT-ANSI.vcxproj =================================================================== --- build/vs2022win7/OpenMPT-ANSI.vcxproj (revision 25320) +++ build/vs2022win7/OpenMPT-ANSI.vcxproj (working copy) @@ -963,6 +963,9 @@ + + + @@ -1428,6 +1431,8 @@ + + @@ -1435,7 +1440,6 @@ - Index: build/vs2022win7/OpenMPT-ANSI.vcxproj.filters =================================================================== --- build/vs2022win7/OpenMPT-ANSI.vcxproj.filters (revision 25320) +++ build/vs2022win7/OpenMPT-ANSI.vcxproj.filters (working copy) @@ -750,6 +750,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2141,6 +2150,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2162,9 +2177,6 @@ mptrack - - mptrack - mptrack Index: build/vs2022win7/OpenMPT-UTF8.vcxproj =================================================================== --- build/vs2022win7/OpenMPT-UTF8.vcxproj (revision 25320) +++ build/vs2022win7/OpenMPT-UTF8.vcxproj (working copy) @@ -963,6 +963,9 @@ + + + @@ -1428,6 +1431,8 @@ + + @@ -1435,7 +1440,6 @@ - Index: build/vs2022win7/OpenMPT-UTF8.vcxproj.filters =================================================================== --- build/vs2022win7/OpenMPT-UTF8.vcxproj.filters (revision 25320) +++ build/vs2022win7/OpenMPT-UTF8.vcxproj.filters (working copy) @@ -750,6 +750,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2141,6 +2150,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2162,9 +2177,6 @@ mptrack - - mptrack - mptrack Index: build/vs2022win7/OpenMPT.vcxproj =================================================================== --- build/vs2022win7/OpenMPT.vcxproj (revision 25320) +++ build/vs2022win7/OpenMPT.vcxproj (working copy) @@ -963,6 +963,9 @@ + + + @@ -1428,6 +1431,8 @@ + + @@ -1435,7 +1440,6 @@ - Index: build/vs2022win7/OpenMPT.vcxproj.filters =================================================================== --- build/vs2022win7/OpenMPT.vcxproj.filters (revision 25320) +++ build/vs2022win7/OpenMPT.vcxproj.filters (working copy) @@ -750,6 +750,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2141,6 +2150,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2162,9 +2177,6 @@ mptrack - - mptrack - mptrack Index: build/vs2022win81/OpenMPT-ANSI.vcxproj =================================================================== --- build/vs2022win81/OpenMPT-ANSI.vcxproj (revision 25320) +++ build/vs2022win81/OpenMPT-ANSI.vcxproj (working copy) @@ -1365,6 +1365,9 @@ + + + @@ -1830,6 +1833,8 @@ + + @@ -1837,7 +1842,6 @@ - Index: build/vs2022win81/OpenMPT-ANSI.vcxproj.filters =================================================================== --- build/vs2022win81/OpenMPT-ANSI.vcxproj.filters (revision 25320) +++ build/vs2022win81/OpenMPT-ANSI.vcxproj.filters (working copy) @@ -750,6 +750,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2141,6 +2150,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2162,9 +2177,6 @@ mptrack - - mptrack - mptrack Index: build/vs2022win81/OpenMPT-UTF8.vcxproj =================================================================== --- build/vs2022win81/OpenMPT-UTF8.vcxproj (revision 25320) +++ build/vs2022win81/OpenMPT-UTF8.vcxproj (working copy) @@ -1365,6 +1365,9 @@ + + + @@ -1830,6 +1833,8 @@ + + @@ -1837,7 +1842,6 @@ - Index: build/vs2022win81/OpenMPT-UTF8.vcxproj.filters =================================================================== --- build/vs2022win81/OpenMPT-UTF8.vcxproj.filters (revision 25320) +++ build/vs2022win81/OpenMPT-UTF8.vcxproj.filters (working copy) @@ -750,6 +750,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2141,6 +2150,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2162,9 +2177,6 @@ mptrack - - mptrack - mptrack Index: build/vs2022win81/OpenMPT.vcxproj =================================================================== --- build/vs2022win81/OpenMPT.vcxproj (revision 25320) +++ build/vs2022win81/OpenMPT.vcxproj (working copy) @@ -1365,6 +1365,9 @@ + + + @@ -1830,6 +1833,8 @@ + + @@ -1837,7 +1842,6 @@ - Index: build/vs2022win81/OpenMPT.vcxproj.filters =================================================================== --- build/vs2022win81/OpenMPT.vcxproj.filters (revision 25320) +++ build/vs2022win81/OpenMPT.vcxproj.filters (working copy) @@ -750,6 +750,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2141,6 +2150,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2162,9 +2177,6 @@ mptrack - - mptrack - mptrack Index: build/vs2022win8/OpenMPT-ANSI.vcxproj =================================================================== --- build/vs2022win8/OpenMPT-ANSI.vcxproj (revision 25320) +++ build/vs2022win8/OpenMPT-ANSI.vcxproj (working copy) @@ -1365,6 +1365,9 @@ + + + @@ -1830,6 +1833,8 @@ + + @@ -1837,7 +1842,6 @@ - Index: build/vs2022win8/OpenMPT-ANSI.vcxproj.filters =================================================================== --- build/vs2022win8/OpenMPT-ANSI.vcxproj.filters (revision 25320) +++ build/vs2022win8/OpenMPT-ANSI.vcxproj.filters (working copy) @@ -750,6 +750,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2141,6 +2150,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2162,9 +2177,6 @@ mptrack - - mptrack - mptrack Index: build/vs2022win8/OpenMPT-UTF8.vcxproj =================================================================== --- build/vs2022win8/OpenMPT-UTF8.vcxproj (revision 25320) +++ build/vs2022win8/OpenMPT-UTF8.vcxproj (working copy) @@ -1365,6 +1365,9 @@ + + + @@ -1830,6 +1833,8 @@ + + @@ -1837,7 +1842,6 @@ - Index: build/vs2022win8/OpenMPT-UTF8.vcxproj.filters =================================================================== --- build/vs2022win8/OpenMPT-UTF8.vcxproj.filters (revision 25320) +++ build/vs2022win8/OpenMPT-UTF8.vcxproj.filters (working copy) @@ -750,6 +750,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2141,6 +2150,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2162,9 +2177,6 @@ mptrack - - mptrack - mptrack Index: build/vs2022win8/OpenMPT.vcxproj =================================================================== --- build/vs2022win8/OpenMPT.vcxproj (revision 25320) +++ build/vs2022win8/OpenMPT.vcxproj (working copy) @@ -1365,6 +1365,9 @@ + + + @@ -1830,6 +1833,8 @@ + + @@ -1837,7 +1842,6 @@ - Index: build/vs2022win8/OpenMPT.vcxproj.filters =================================================================== --- build/vs2022win8/OpenMPT.vcxproj.filters (revision 25320) +++ build/vs2022win8/OpenMPT.vcxproj.filters (working copy) @@ -750,6 +750,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2141,6 +2150,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2162,9 +2177,6 @@ mptrack - - mptrack - mptrack Index: build/vs2026win11/OpenMPT-ANSI.vcxproj =================================================================== --- build/vs2026win11/OpenMPT-ANSI.vcxproj (revision 25320) +++ build/vs2026win11/OpenMPT-ANSI.vcxproj (working copy) @@ -1767,6 +1767,9 @@ + + + @@ -2232,6 +2235,8 @@ + + @@ -2239,7 +2244,6 @@ - Index: build/vs2026win11/OpenMPT-ANSI.vcxproj.filters =================================================================== --- build/vs2026win11/OpenMPT-ANSI.vcxproj.filters (revision 25320) +++ build/vs2026win11/OpenMPT-ANSI.vcxproj.filters (working copy) @@ -750,6 +750,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2141,6 +2150,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2162,9 +2177,6 @@ mptrack - - mptrack - mptrack Index: build/vs2026win11/OpenMPT-UTF8.vcxproj =================================================================== --- build/vs2026win11/OpenMPT-UTF8.vcxproj (revision 25320) +++ build/vs2026win11/OpenMPT-UTF8.vcxproj (working copy) @@ -1767,6 +1767,9 @@ + + + @@ -2232,6 +2235,8 @@ + + @@ -2239,7 +2244,6 @@ - Index: build/vs2026win11/OpenMPT-UTF8.vcxproj.filters =================================================================== --- build/vs2026win11/OpenMPT-UTF8.vcxproj.filters (revision 25320) +++ build/vs2026win11/OpenMPT-UTF8.vcxproj.filters (working copy) @@ -750,6 +750,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2141,6 +2150,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2162,9 +2177,6 @@ mptrack - - mptrack - mptrack Index: build/vs2026win11/OpenMPT.vcxproj =================================================================== --- build/vs2026win11/OpenMPT.vcxproj (revision 25320) +++ build/vs2026win11/OpenMPT.vcxproj (working copy) @@ -1767,6 +1767,9 @@ + + + @@ -2232,6 +2235,8 @@ + + @@ -2239,7 +2244,6 @@ - Index: build/vs2026win11/OpenMPT.vcxproj.filters =================================================================== --- build/vs2026win11/OpenMPT.vcxproj.filters (revision 25320) +++ build/vs2026win11/OpenMPT.vcxproj.filters (working copy) @@ -750,6 +750,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2141,6 +2150,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2162,9 +2177,6 @@ mptrack - - mptrack - mptrack Index: build/vs2026win11clang/OpenMPT-ANSI.vcxproj =================================================================== --- build/vs2026win11clang/OpenMPT-ANSI.vcxproj (revision 25320) +++ build/vs2026win11clang/OpenMPT-ANSI.vcxproj (working copy) @@ -1323,6 +1323,9 @@ + + + @@ -1788,6 +1791,8 @@ + + @@ -1795,7 +1800,6 @@ - Index: build/vs2026win11clang/OpenMPT-ANSI.vcxproj.filters =================================================================== --- build/vs2026win11clang/OpenMPT-ANSI.vcxproj.filters (revision 25320) +++ build/vs2026win11clang/OpenMPT-ANSI.vcxproj.filters (working copy) @@ -711,6 +711,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2102,6 +2111,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2123,9 +2138,6 @@ mptrack - - mptrack - mptrack Index: build/vs2026win11clang/OpenMPT-UTF8.vcxproj =================================================================== --- build/vs2026win11clang/OpenMPT-UTF8.vcxproj (revision 25320) +++ build/vs2026win11clang/OpenMPT-UTF8.vcxproj (working copy) @@ -1323,6 +1323,9 @@ + + + @@ -1788,6 +1791,8 @@ + + @@ -1795,7 +1800,6 @@ - Index: build/vs2026win11clang/OpenMPT-UTF8.vcxproj.filters =================================================================== --- build/vs2026win11clang/OpenMPT-UTF8.vcxproj.filters (revision 25320) +++ build/vs2026win11clang/OpenMPT-UTF8.vcxproj.filters (working copy) @@ -711,6 +711,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2102,6 +2111,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2123,9 +2138,6 @@ mptrack - - mptrack - mptrack Index: build/vs2026win11clang/OpenMPT.vcxproj =================================================================== --- build/vs2026win11clang/OpenMPT.vcxproj (revision 25320) +++ build/vs2026win11clang/OpenMPT.vcxproj (working copy) @@ -1323,6 +1323,9 @@ + + + @@ -1788,6 +1791,8 @@ + + @@ -1795,7 +1800,6 @@ - Index: build/vs2026win11clang/OpenMPT.vcxproj.filters =================================================================== --- build/vs2026win11clang/OpenMPT.vcxproj.filters (revision 25320) +++ build/vs2026win11clang/OpenMPT.vcxproj.filters (working copy) @@ -711,6 +711,15 @@ mptrack + + mptrack + + + mptrack + + + mptrack + mptrack @@ -2102,6 +2111,12 @@ mptrack + + mptrack + + + mptrack + mptrack @@ -2123,9 +2138,6 @@ mptrack - - mptrack - mptrack Index: mptrack/AbstractVstEditor.cpp =================================================================== --- mptrack/AbstractVstEditor.cpp (revision 25320) +++ mptrack/AbstractVstEditor.cpp (working copy) @@ -90,7 +90,6 @@ ON_COMMAND(ID_VSTPRESETNAME, &CAbstractVstEditor::OnVSTPresetRename) ON_COMMAND(ID_PLUGINTOINSTRUMENT, &CAbstractVstEditor::OnCreateInstrument) ON_COMMAND_RANGE(ID_PRESET_SET, ID_PRESET_SET + PRESETS_PER_GROUP, &CAbstractVstEditor::OnSetPreset) - ON_MESSAGE(WM_MOD_MIDIMSG, &CAbstractVstEditor::OnMidiMsg) ON_MESSAGE(WM_MOD_KEYCOMMAND, &CAbstractVstEditor::OnCustomKeyMsg) //rewbs.customKeys ON_COMMAND_RANGE(ID_PLUGSELECT, ID_PLUGSELECT + MAX_MIXPLUGINS, &CAbstractVstEditor::OnToggleEditor) //rewbs.patPlugName ON_COMMAND_RANGE(ID_SELECTINST, ID_SELECTINST + MAX_INSTRUMENTS, &CAbstractVstEditor::OnSetInputInstrument) //rewbs.patPlugName @@ -149,24 +148,29 @@ { ResizableDialog::OnActivate(nState, pWndOther, bMinimized); if(nState != WA_INACTIVE) - { - auto callback = [&plugin = m_VstPlugin](mpt::const_byte_span sysex) { plugin.MidiSend(sysex); }; - CMainFrame::GetMainFrame()->SetMidiRecordWnd(GetSafeHwnd(), callback); - } + CMainFrame::GetMainFrame()->SetMidiRecordCallback(this, MidiTransformers::Transpose); } -LRESULT CAbstractVstEditor::OnMidiMsg(WPARAM midiData, LPARAM sender) +bool CAbstractVstEditor::PreFilterMidiMessage(mpt::const_byte_span midiData) { + return DefaultPreFilterMidiMessage(midiData, &m_VstPlugin.GetSoundFile(), kCtxVSTGUI); +} + + +void CAbstractVstEditor::OnMidiMessage(mpt::const_byte_span data) +{ + // Prevent MIDI feedback + if(data.empty() || CMainFrame::GetMainFrame()->GetCurrentMidiSender() == &m_VstPlugin) + return; + CModDoc *modDoc = m_VstPlugin.GetModDoc(); - if(modDoc != nullptr && sender != reinterpret_cast(&m_VstPlugin)) + if(modDoc != nullptr) { if(!CheckInstrument(m_nInstrument)) m_nInstrument = GetBestInstrumentCandidate(); - modDoc->ProcessMIDI((uint32)midiData, 0, m_nInstrument, &m_VstPlugin, kCtxVSTGUI); - return 1; + modDoc->ProcessMIDI(data, 0, m_nInstrument, &m_VstPlugin); } - return 0; } Index: mptrack/AbstractVstEditor.h =================================================================== --- mptrack/AbstractVstEditor.h (revision 25320) +++ mptrack/AbstractVstEditor.h (working copy) @@ -12,6 +12,7 @@ #include "openmpt/all/BuildSettings.hpp" +#include "MidiInputCallback.h" #include "ResizableDialog.h" #include "Moddoc.h" #include "../soundlib/Snd_defs.h" @@ -21,7 +22,7 @@ class IMixPlugin; struct UpdateHint; -class CAbstractVstEditor : public ResizableDialog +class CAbstractVstEditor : public ResizableDialog, public IMidiInputCallback { protected: CMenu m_Menu; @@ -85,7 +86,6 @@ afx_msg void OnCreateInstrument(); afx_msg void OnMenuSelect(UINT nItemID, UINT nFlags, HMENU hMenu); afx_msg LRESULT OnCustomKeyMsg(WPARAM, LPARAM); - afx_msg LRESULT OnMidiMsg(WPARAM, LPARAM); afx_msg void OnDropFiles(HDROP hDropInfo); afx_msg void OnMove(int x, int y); afx_msg void OnClose() { DoClose(); } @@ -94,6 +94,9 @@ void PostNcDestroy() override; void OnOK() override { DoClose(); } void OnCancel() override { DoClose(); } + BOOL PreTranslateMessage(MSG *msg) override; + bool PreFilterMidiMessage(mpt::const_byte_span midiData) override; + void OnMidiMessage(mpt::const_byte_span data) override; virtual bool OpenEditor(CWnd *parent); virtual void DoClose(); @@ -109,7 +112,6 @@ DECLARE_MESSAGE_MAP() protected: - BOOL PreTranslateMessage(MSG *msg) override; bool HandleKeyMessage(MSG &msg, bool handleGlobal = false); void UpdatePresetMenu(bool force = false); void GeneratePresetMenu(int32 offset, CMenu &parent); Index: mptrack/Childfrm.cpp =================================================================== --- mptrack/Childfrm.cpp (revision 25320) +++ mptrack/Childfrm.cpp (working copy) @@ -147,7 +147,7 @@ if(bActivate && m_hWndView) { // Need this in addition to OnMDIActivate when switching from a non-MDI window such as a plugin editor - CMainFrame::GetMainFrame()->SetMidiRecordWnd(m_hWndView); + UpdateMidiRecordCallback(); } if(m_hWndCtrl) ::SendMessage(m_hWndCtrl, bActivate ? WM_MOD_MDIACTIVATE : WM_MOD_MDIDEACTIVATE, 0, 0); @@ -166,7 +166,7 @@ { MPT_ASSERT(pActivateWnd == this); CMainFrame::GetMainFrame()->UpdateEffectKeys(static_cast(GetActiveDocument())); - CMainFrame::GetMainFrame()->SetMidiRecordWnd(m_hWndView); + UpdateMidiRecordCallback(); m_lastActiveFrame = this; } if(m_hWndCtrl) @@ -244,20 +244,16 @@ { CMainFrame *pMainFrm = CMainFrame::GetMainFrame(); CWnd *pWnd; - if (pViewClass->m_lpszClassName == m_currentViewClassName) return TRUE; + if(pViewClass->m_lpszClassName == m_currentViewClassName) + return TRUE; if(!m_currentViewClassName.empty()) { + if(pMainFrm->GetMidiRecordCallback() == dynamic_cast(m_wndSplitter.GetPane(1, 0))) + pMainFrm->SetMidiRecordCallback(nullptr); m_currentViewClassName.clear(); m_wndSplitter.DeleteView(1, 0); } - if ((m_hWndView) && (pMainFrm)) - { - if (pMainFrm->GetMidiRecordWnd() == m_hWndView) - { - pMainFrm->SetMidiRecordWnd(NULL); - } - } - m_hWndView = NULL; + m_hWndView = nullptr; if (!m_wndSplitter.CreateView(1, 0, pViewClass, CSize(0, 0), pContext)) return FALSE; // Get 2nd window handle if ((pWnd = m_wndSplitter.GetPane(1, 0)) != NULL) m_hWndView = pWnd->m_hWnd; @@ -267,7 +263,7 @@ { ::PostMessage(m_hWndView, WM_MOD_VIEWMSG, VIEWMSG_SETCTRLWND, (LPARAM)m_hWndCtrl); ::PostMessage(m_hWndCtrl, WM_MOD_CTRLMSG, CTRLMSG_SETVIEWWND, (LPARAM)m_hWndView); - pMainFrm->SetMidiRecordWnd(m_hWndView); + UpdateMidiRecordCallback(); } return TRUE; } @@ -376,6 +372,13 @@ } +void CChildFrame::UpdateMidiRecordCallback() const +{ + CWnd *viewWnd = m_wndSplitter.GetPane(1, 0); + CMainFrame::GetMainFrame()->SetMidiRecordCallback(dynamic_cast(viewWnd)); +} + + ///////////////////////////////////////////////////////////////////////////// // CChildFrame message handlers Index: mptrack/Childfrm.h =================================================================== --- mptrack/Childfrm.h (revision 25320) +++ mptrack/Childfrm.h (working copy) @@ -98,8 +98,6 @@ static CChildFrame *m_lastActiveFrame; static int glMdiOpenCount; -// Attributes -protected: CSplitterWnd m_wndSplitter; HWND m_hWndCtrl = nullptr, m_hWndView = nullptr; GeneralViewState m_ViewGeneral; @@ -112,7 +110,6 @@ bool m_maxWhenClosed = false; bool m_initialActivation = true; -// Operations public: CModControlView *GetModControlView() const { return reinterpret_cast(m_wndSplitter.GetPane(0, 0)); } BOOL ChangeViewClass(CRuntimeClass *pNewViewClass, CCreateContext *pContext = nullptr); @@ -142,18 +139,17 @@ static CChildFrame *LastActiveFrame() { return m_lastActiveFrame; } -// Overrides - // ClassWizard generated virtual function overrides +public: //{{AFX_VIRTUAL(CChildFrame) -public: - BOOL OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) override; + BOOL OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext *pContext) override; BOOL PreCreateWindow(CREATESTRUCT& cs) override; void ActivateFrame(int nCmdShow) override; void OnUpdateFrameTitle(BOOL bAddToTitle) override; //}}AFX_VIRTUAL -// Generated message map functions protected: + void UpdateMidiRecordCallback() const; + //{{AFX_MSG(CChildFrame) afx_msg LRESULT OnDPIChangedAfterParent(WPARAM, LPARAM); afx_msg void OnDestroy(); Index: mptrack/Globals.cpp =================================================================== --- mptrack/Globals.cpp (revision 25320) +++ mptrack/Globals.cpp (working copy) @@ -738,10 +738,8 @@ pModDoc->SetNotifications(Notification::Default); pModDoc->SetFollowWnd(NULL); } - if (pMainFrm->GetMidiRecordWnd() == m_hWnd) - { - pMainFrm->SetMidiRecordWnd(NULL); - } + if(pMainFrm->GetMidiRecordCallback() == dynamic_cast(this)) + pMainFrm->SetMidiRecordCallback(nullptr); } CScrollView::OnDestroy(); } @@ -894,4 +892,23 @@ } +bool DefaultPreFilterMidiMessage(mpt::const_byte_span midiData, CSoundFile *sndFile, InputTargetContext ctx) +{ + PLUGINDEX mappedIndex = 0; + PlugParamIndex paramIndex = 0; + uint16 paramValue = 0; + bool captured = sndFile ? sndFile->GetMIDIMapper().OnMIDImsg(midiData, mappedIndex, paramIndex, paramValue) : false; + + // Handle MIDI messages assigned to shortcuts + CInputHandler *ih = CMainFrame::GetInputHandler(); + if(ih->HandleMIDIMessage(ctx, midiData) != kcNull + || ih->HandleMIDIMessage(kCtxAllContexts, midiData) != kcNull) + { + // Mapped to a command, no need to pass message on. + captured = true; + } + return captured; +} + + OPENMPT_NAMESPACE_END Index: mptrack/Globals.h =================================================================== --- mptrack/Globals.h (revision 25320) +++ mptrack/Globals.h (working copy) @@ -24,6 +24,7 @@ class CSoundFile; struct DRAGONDROP; struct Notification; +enum InputTargetContext : int8; class CModControlBar: public CToolBarCtrl { @@ -253,4 +254,7 @@ }; +bool DefaultPreFilterMidiMessage(mpt::const_byte_span midiData, CSoundFile *sndFile, InputTargetContext ctx); + + OPENMPT_NAMESPACE_END Index: mptrack/InputHandler.cpp =================================================================== --- mptrack/InputHandler.cpp (revision 25320) +++ mptrack/InputHandler.cpp (working copy) @@ -176,10 +176,13 @@ // Translate MIDI messages to shortcut commands -CommandID CInputHandler::HandleMIDIMessage(InputTargetContext context, uint32 message) +CommandID CInputHandler::HandleMIDIMessage(InputTargetContext context, mpt::const_byte_span message) { + if(message.empty()) + return kcNull; KeyMapRange cmd = { m_keyMap.end(), m_keyMap.end() }; - auto byte1 = MIDIEvents::GetDataByte1FromEvent(message), byte2 = MIDIEvents::GetDataByte2FromEvent(message); + uint8 byte1 = MIDIEvents::GetDataByte1FromEvent(message); + uint8 byte2 = MIDIEvents::GetDataByte2FromEvent(message); switch(MIDIEvents::GetTypeFromEvent(message)) { case MIDIEvents::evControllerChange: Index: mptrack/InputHandler.h =================================================================== --- mptrack/InputHandler.h (revision 25320) +++ mptrack/InputHandler.h (working copy) @@ -47,7 +47,7 @@ static KeyEventType GetKeyEventType(const MSG &msg); static KeyEventType GetKeyEventType(UINT nFlags); bool IsKeyPressHandledByTextBox(DWORD wparam, HWND hWnd) const; - CommandID HandleMIDIMessage(InputTargetContext context, uint32 message); + CommandID HandleMIDIMessage(InputTargetContext context, mpt::const_byte_span message); int GetKeyListSize(CommandID cmd) const; Index: mptrack/KeyConfigDlg.cpp =================================================================== --- mptrack/KeyConfigDlg.cpp (revision 25320) +++ mptrack/KeyConfigDlg.cpp (working copy) @@ -34,16 +34,14 @@ BEGIN_MESSAGE_MAP(CCustEdit, CEdit) ON_WM_SETFOCUS() ON_WM_KILLFOCUS() - ON_MESSAGE(WM_MOD_MIDIMSG, &CCustEdit::OnMidiMsg) END_MESSAGE_MAP() -LRESULT CCustEdit::OnMidiMsg(WPARAM dwMidiDataParam, LPARAM) +void CCustEdit::OnMidiMessage(mpt::const_byte_span midiData) { - if(!m_isFocussed) - return 1; + if(midiData.empty()) + return; - uint32 midiData = static_cast(dwMidiDataParam); const auto byte1 = MIDIEvents::GetDataByte1FromEvent(midiData), byte2 = MIDIEvents::GetDataByte2FromEvent(midiData); switch(MIDIEvents::GetTypeFromEvent(midiData)) { @@ -64,8 +62,6 @@ default: break; } - - return 1; } @@ -104,9 +100,7 @@ // Lock the input handler CMainFrame::GetInputHandler()->Bypass(true); // Accept MIDI input - CMainFrame::GetMainFrame()->SetMidiRecordWnd(m_hWnd); - - m_isFocussed = true; + CMainFrame::GetMainFrame()->SetMidiRecordCallback(this, MidiTransformers::None); } @@ -113,9 +107,9 @@ void CCustEdit::OnKillFocus(CWnd *pNewWnd) { CEdit::OnKillFocus(pNewWnd); - //unlock the input handler + // Unlock the input handler CMainFrame::GetInputHandler()->Bypass(false); - m_isFocussed = false; + CMainFrame::GetMainFrame()->SetMidiRecordCallback(nullptr); m_pOptKeyDlg->OnCancelKeyChoice(this); } Index: mptrack/KeyConfigDlg.h =================================================================== --- mptrack/KeyConfigDlg.h (revision 25320) +++ mptrack/KeyConfigDlg.h (working copy) @@ -13,6 +13,7 @@ #include "openmpt/all/BuildSettings.hpp" #include "CListCtrl.h" #include "CommandSet.h" +#include "MidiInputCallback.h" OPENMPT_NAMESPACE_BEGIN @@ -36,11 +37,10 @@ }; -class CCustEdit: public CEdit +class CCustEdit: public CEdit, public IMidiInputCallback { protected: COptionsKeyboard *m_pOptKeyDlg = nullptr; - bool m_isFocussed = false; bool m_bypassed = false; public: @@ -56,10 +56,10 @@ protected: BOOL PreTranslateMessage(MSG *pMsg) override; - + void OnMidiMessage(mpt::const_byte_span midiData) override; + afx_msg void OnSetFocus(CWnd *pOldWnd); afx_msg void OnKillFocus(CWnd *pNewWnd); - afx_msg LRESULT OnMidiMsg(WPARAM, LPARAM); DECLARE_MESSAGE_MAP() }; Index: mptrack/Mainbar.cpp =================================================================== --- mptrack/Mainbar.cpp (revision 25320) +++ mptrack/Mainbar.cpp (working copy) @@ -14,6 +14,7 @@ #include "ImageLists.h" #include "InputHandler.h" #include "Mainfrm.h" +#include "MidiDeviceManager.h" #include "Moddoc.h" #include "Mptrack.h" #include "resource.h" @@ -566,6 +567,9 @@ wsprintf(s, _T("Octave %d"), octave); m_EditOctave.SetWindowText(s); m_SpinOctave.SetPos(octave); + + if(CMainFrame *mainFrm = CMainFrame::GetMainFrame()) + mainFrm->UpdateMidiFilters(); } @@ -852,19 +856,16 @@ // Show a list of MIDI devices { HMENU hMenu = ::CreatePopupMenu(); - MIDIINCAPS mic; - UINT numDevs = midiInGetNumDevs(); - if(numDevs > MAX_MIDI_DEVICES) numDevs = MAX_MIDI_DEVICES; + auto ports = MidiDeviceManager::Instance().EnumerateInputPorts(); + if(ports.size() > MAX_MIDI_DEVICES) + ports.resize(MAX_MIDI_DEVICES); UINT current = TrackerSettings::Instance().GetCurrentMIDIDevice(); - for(UINT i = 0; i < numDevs; i++) + for(const auto &port : ports) { - mic.szPname[0] = 0; - if(midiInGetDevCaps(i, &mic, sizeof(mic)) == MMSYSERR_NOERROR) - { - ::AppendMenu(hMenu, MF_STRING | (i == current ? MF_CHECKED : 0), ID_SELECT_MIDI_DEVICE + i, theApp.GetFriendlyMIDIPortName(mpt::String::ReadCStringBuf(mic.szPname), true)); - } + CString displayName = theApp.GetFriendlyMIDIPortName(mpt::ToCString(mpt::Charset::UTF8, port.name), true); + ::AppendMenu(hMenu, MF_STRING | (port.id == current ? MF_CHECKED : 0), ID_SELECT_MIDI_DEVICE + port.id, displayName); } - if(!numDevs) + if(ports.empty()) { ::AppendMenu(hMenu, MF_STRING | MF_GRAYED, 0, _T("No MIDI input devices found")); } @@ -880,9 +881,9 @@ void CMainToolBar::OnSelectMIDIDevice(UINT id) { - CMainFrame::GetMainFrame()->midiCloseDevice(); + CMainFrame::GetMainFrame()->MidiCloseDevice(); TrackerSettings::Instance().SetMIDIDevice(id - ID_SELECT_MIDI_DEVICE); - CMainFrame::GetMainFrame()->midiOpenDevice(); + CMainFrame::GetMainFrame()->MidiOpenDevice(); } Index: mptrack/MainFrm.cpp =================================================================== --- mptrack/MainFrm.cpp (revision 25320) +++ mptrack/MainFrm.cpp (working copy) @@ -27,6 +27,7 @@ #include "InputHandler.h" #include "IPCWindow.h" #include "KeyConfigDlg.h" +#include "MidiDeviceManager.h" #include "Moddoc.h" #include "ModDocTemplate.h" #include "Mpdlgs.h" @@ -121,6 +122,7 @@ ON_MESSAGE(WM_MOD_UPDATEPOSITION, &CMainFrame::OnUpdatePosition) ON_MESSAGE(WM_MOD_INVALIDATEPATTERNS, &CMainFrame::OnInvalidatePatterns) + ON_MESSAGE(WM_MOD_MIDIMSG, &CMainFrame::OnMidiMsgReady) ON_MESSAGE(WM_MOD_KEYCOMMAND, &CMainFrame::OnCustomKeyMsg) ON_MESSAGE(WM_MOD_MIDIMAPPING, &CMainFrame::OnViewMIDIMapping) ON_MESSAGE(WM_MOD_UPDATEVIEWS, &CMainFrame::OnUpdateViews) @@ -328,7 +330,7 @@ UpdateColors(); if(TrackerSettings::Instance().midiSetup & MidiSetup::EnableMidiInOnStartup) - midiOpenDevice(false); + MidiOpenDevice(false); #if MPT_COMPILER_MSVC #pragma warning(push) @@ -425,8 +427,8 @@ KillTimer(m_nTimer); m_nTimer = 0; } - if(midiInData.inHandle) - midiCloseDevice(); + if(m_midiInputHandle) + MidiCloseDevice(); // Delete bitmaps delete bmpNotes; bmpNotes = nullptr; @@ -498,12 +500,11 @@ BOOL CMainFrame::OnDeviceChange(UINT nEventType, DWORD_PTR dwData) { - if(nEventType == DBT_DEVNODES_CHANGED && midiInData.inHandle) + if(nEventType == DBT_DEVNODES_CHANGED && m_midiInputHandle) { - // Calling this (or most other MIDI input related functions) makes the MIDI driver realize - // that the connection to USB MIDI devices was lost and send a MIM_CLOSE message. - // Otherwise, after disconnecting a USB MIDI device, the MIDI callback will stay alive forever but return no data. - midiInGetNumDevs(); + // Check if USB MIDI device was disconnected + if(!MidiDeviceManager::CheckDeviceStatus(*m_midiInputHandle)) + SendMessage(WM_COMMAND, ID_MIDI_RECORD); } return CMDIFrameWnd::OnDeviceChange(nEventType, dwData); } @@ -782,6 +783,7 @@ void CMainFrame::SoundCallbackPreStart() { MPT_TRACE(); + MidiDeviceManager::Instance().ResetSyncPoint(); m_SoundDeviceClock.SetResolution(1); } @@ -923,6 +925,7 @@ MPT_ASSERT(InAudioThread()); OPENMPT_PROFILE_FUNCTION(Profiler::Notify); MPT_ASSERT((timeInfo.RenderStreamPositionAfter.Frames - timeInfo.RenderStreamPositionBefore.Frames) < std::numeric_limits::max()); + MidiDeviceManager::Instance().UpdateSyncPoint(timeInfo.SyncPointStreamFrames, timeInfo.SyncPointSystemTimestamp, timeInfo.Speed, m_pSndFile->GetSampleRate()); samplecount_t framesRendered = static_cast(timeInfo.RenderStreamPositionAfter.Frames - timeInfo.RenderStreamPositionBefore.Frames); int64 streamPosition = timeInfo.RenderStreamPositionAfter.Frames; DoNotification(framesRendered, streamPosition); @@ -1521,6 +1524,7 @@ // set mixing parameters in CSoundFile UpdateAudioParameters(sndFile); + mpt::reconstruct(sndFile.m_TimingInfo); SetPlaybackSoundFile(&sndFile); @@ -1579,9 +1583,10 @@ SetElapsedTime(0); GenerateStopNotification(); - m_pSndFile->ResetPlayPos(); - m_pSndFile->ResetChannels(); + CSoundFile &sndFile = *m_pSndFile; UnsetPlaybackSoundFile(); + sndFile.ResetPlayPos(); + sndFile.ResetChannels(); StopPlayback(); @@ -1955,20 +1960,32 @@ } -void CMainFrame::SetupMidi(FlagSet d, UINT n) +void CMainFrame::SetupMidi(FlagSet flags, UINT device) { - bool deviceChanged = (TrackerSettings::Instance().m_nMidiDevice != n); - TrackerSettings::Instance().midiSetup = d; - TrackerSettings::Instance().SetMIDIDevice(n); - if(deviceChanged && midiInData.inHandle) + bool deviceChanged = (TrackerSettings::Instance().m_nMidiDevice != device); + TrackerSettings::Instance().midiSetup = flags; + TrackerSettings::Instance().SetMIDIDevice(device); + if(deviceChanged && m_midiInputHandle) { // Device has changed, close the old one. - midiCloseDevice(); - midiOpenDevice(); + MidiCloseDevice(); + MidiOpenDevice(); } + + UpdateMidiFilters(); } +void CMainFrame::UpdateMidiFilters() +{ + FlagSet midiSetup = TrackerSettings::Instance().midiSetup; + m_volumeFilter.SetRecordVelocity(midiSetup[MidiSetup::RecordVelocity]); + m_volumeFilter.SetApplyChannelVolumeToVelocity(midiSetup[MidiSetup::ApplyChannelVolumeToVelocity]); + m_volumeFilter.SetVelocityAmplification(TrackerSettings::Instance().midiVelocityAmp); + m_transposeFilter.SetTranspose(midiSetup[MidiSetup::TransposeKeyboard] ? (GetBaseOctave() - 4) * 12 : 0); +} + + void CMainFrame::SetUserText(const TCHAR *text) { if(text[0] || !m_userText.IsEmpty()) @@ -2568,6 +2585,133 @@ } +bool CMainFrame::MidiOpenDevice(bool showSettings) +{ + if(m_midiInputHandle) + return true; + + UpdateMidiFilters(); + + MidiDeviceManager &manager = MidiDeviceManager::Instance(); + MidiPortID port = TrackerSettings::Instance().GetCurrentMIDIDevice(); + m_midiInputHandle = manager.OpenInput(port, this); + + if(!m_midiInputHandle) + { + // Show MIDI configuration on fail. + if(showSettings) + { + CMainFrame::m_nLastOptionsPage = OPTIONS_PAGE_MIDI; + OnViewOptions(); + } + + // Let's see if the user updated the settings. + port = static_cast(TrackerSettings::Instance().GetCurrentMIDIDevice()); + m_midiInputHandle = manager.OpenInput(port, this); + + if(!m_midiInputHandle) + return false; + } + + return true; +} + + +void CMainFrame::MidiCloseDevice() +{ + m_midiInputHandle.reset(); +} + + +void CMainFrame::OnMidiRecord() +{ + if(m_midiInputHandle) + MidiCloseDevice(); + else + MidiOpenDevice(); +} + + +void CMainFrame::OnUpdateMidiRecord(CCmdUI *pCmdUI) +{ + if(pCmdUI) + pCmdUI->SetCheck(m_midiInputHandle ? TRUE : FALSE); +} + + +void CMainFrame::SetMidiRecordCallback(IMidiInputCallback *callback, FlagSet transformers) +{ + m_sustainFilter.SetBypassed(!transformers[MidiTransformers::Sustain]); + m_volumeFilter.SetBypassed(!transformers[MidiTransformers::Volume]); + m_transposeFilter.SetBypassed(!transformers[MidiTransformers::Transpose]); + + m_midiRecordCallback = callback; + if(callback) + callback->SetDestroyNotifySink(this); +} + + +void CMainFrame::OnMidiInputCallbackDestroyed(void *callback) +{ + if(m_midiRecordCallback == callback) + m_midiRecordCallback = nullptr; +} + + +void CMainFrame::EnqueueMidiMessage(mpt::const_byte_span data, void *sender) +{ + { + mpt::lock_guard lock{m_midiInputMutex}; + m_midiInputQueue.emplace_back(0, data, sender); + } + if(!m_midiMsgPending.exchange(true)) + PostMessage(WM_MOD_MIDIMSG, 0, 0); +} + + +LRESULT CMainFrame::OnMidiMsgReady(WPARAM, LPARAM) +{ + m_midiMsgPending = false; + + std::vector localQueue; + { + mpt::lock_guard lock{m_midiInputMutex}; + std::swap(localQueue, m_midiInputQueue); + m_midiInputQueue.reserve(localQueue.capacity()); + } + + for(auto &msg : localQueue) + { + m_currentMidiSender = msg.m_sender; + + if(m_midiRecordCallback) + { + if(m_midiRecordCallback->PreFilterMidiMessage(msg)) + continue; + + m_sustainFilter.SetNextCallback(&m_volumeFilter); + m_volumeFilter.SetNextCallback(&m_transposeFilter); + m_transposeFilter.SetNextCallback(m_midiRecordCallback); + + m_sustainFilter.OnMidiMessage(msg); + } else + { + // No recording target, just handle keyboard shortcuts + GetInputHandler()->HandleMIDIMessage(kCtxAllContexts, msg); + } + } + m_currentMidiSender = nullptr; + + return 0; +} + + +int CMainFrame::NoteVolumeFromMidi(mpt::const_byte_span midiData) +{ + return GetMainFrame()->m_volumeFilter.ApplyVolumeSettings(midiData); +} + + LRESULT CMainFrame::OnUpdatePosition(WPARAM, LPARAM lParam) { OPENMPT_PROFILE_FUNCTION(Profiler::GUI); @@ -3367,18 +3511,16 @@ } -HMENU CMainFrame::CreateFileMenu(const size_t maxCount, std::vector& paths, const mpt::PathString &folderName, const uint16 idRangeBegin) +HMENU CMainFrame::CreateFileMenu(const size_t maxCount, std::vector &paths, const mpt::PathString &folderName, const uint16 idRangeBegin) { paths.clear(); - for(size_t i = 0; i < 2; i++) // 0: app items, 1: user items + std::vector basePaths{theApp.GetInstallPath()}; + if(mpt::PathString configPath = theApp.GetConfigPath(); mpt::PathCompareNoCase(basePaths.front(), configPath)) + basePaths.push_back(std::move(configPath)); + + for(mpt::PathString basePath : basePaths) { - // To avoid duplicates, check whether app path and config path are the same. - if(i == 1 && mpt::PathCompareNoCase(theApp.GetInstallPath(), theApp.GetConfigPath()) == 0) - break; - - mpt::PathString basePath; - basePath = (i == 0) ? theApp.GetInstallPath() : theApp.GetConfigPath(); basePath += folderName; if(!mpt::native_fs{}.is_directory(basePath)) continue; Index: mptrack/Mainfrm.h =================================================================== --- mptrack/Mainfrm.h (revision 25320) +++ mptrack/Mainfrm.h (working copy) @@ -13,6 +13,8 @@ #include "openmpt/all/BuildSettings.hpp" #include "CImageListEx.h" #include "Mainbar.h" +#include "MidiInputCallback.h" +#include "MidiTransformer.h" #include "Notification.h" #include "openmpt/soundbase/Dither.hpp" #include "Settings.h" @@ -27,6 +29,7 @@ #include +#include #include OPENMPT_NAMESPACE_BEGIN @@ -35,6 +38,8 @@ class CDLSBank; class CInputHandler; class CModDoc; +class MidiInputHandle; +class MidiMessage; class QuickStartDlg; struct UpdateCheckResult; struct UpdateHint; @@ -143,6 +148,8 @@ , public SoundDevice::CallbackBufferHandler , public SoundDevice::IMessageReceiver , public TfLanguageProfileNotifySink + , public IMidiInputCallbackDestroyedNotifySink + , public IMidiInputCallback { DECLARE_DYNAMIC(CMainFrame) // static data @@ -174,24 +181,7 @@ DWORD m_AudioThreadId = 0; bool m_InNotifyHandler = false; - // Midi Input public: - struct MidiInData - { - struct SysExBuffer - { - MIDIHDR header; - std::vector data; - }; - - std::vector sysexBuffers; - mpt::mutex dataMutex; - HMIDIIN inHandle = nullptr; - }; - - MidiInData midiInData; - -public: CImageListEx m_MiscIcons, m_MiscIconsDisabled; // Misc Icons CImageListEx m_PatternIcons, m_PatternIconsDisabled; // Pattern icons (includes some from sample editor as well...) CImageListEx m_EnvelopeIcons; // Instrument editor icons @@ -202,8 +192,18 @@ CStatusBar m_wndStatusBar; CMainToolBar m_wndToolBar; CSoundFile *m_pSndFile = nullptr; // != NULL only when currently playing or rendering - HWND m_hWndMidi = nullptr; - std::function m_midiSysExCallback; + + // MIDI Input + std::unique_ptr m_midiInputHandle; + IMidiInputCallback *m_midiRecordCallback = nullptr; + MidiTransposeFilter m_transposeFilter; + MidiVolumeFilter m_volumeFilter; + MidiSustainFilter m_sustainFilter; + mpt::mutex m_midiInputMutex; // Protects m_midiInputQueue + std::vector m_midiInputQueue; + std::atomic m_midiMsgPending = false; + void *m_currentMidiSender = nullptr; // Set during dispatch for feedback prevention + samplecount_t m_dwTimeSec = 0; UINT_PTR m_nTimer = 0; UINT m_nAvgMixChn = 0, m_nMixChn = 0; @@ -268,23 +268,24 @@ bool IsAudioDeviceOpen() const; bool DoNotification(DWORD dwSamplesRead, int64 streamPosition); -// Midi Input Functions public: - bool midiOpenDevice(bool showSettings = true); - void midiCloseDevice(); - void SetMidiRecordWnd(HWND hwnd, std::function sysExCallback = {}) - { - m_hWndMidi = hwnd; - m_midiSysExCallback = std::move(sysExCallback); - } - HWND GetMidiRecordWnd() const { return m_hWndMidi; } - auto GetMidiSysexCallback() const { return m_midiSysExCallback; } + // MIDI Input Functions + bool MidiOpenDevice(bool showSettings = true); + void MidiCloseDevice(); + void OnMidiMessage(mpt::const_byte_span midiData) override { EnqueueMidiMessage(midiData); } + void SetMidiRecordCallback(IMidiInputCallback *callback, FlagSet transformers = MidiTransformers::Default); + IMidiInputCallback *GetMidiRecordCallback() const { return m_midiRecordCallback; } + void OnMidiInputCallbackDestroyed(void *callback) override; + // Enqueue a received MIDI message for processing in UI thread + void EnqueueMidiMessage(mpt::const_byte_span data, void *sender = nullptr); + // Returns the sender of the MIDI message currently being dispatched (for feedback prevention) + void *GetCurrentMidiSender() const { return m_currentMidiSender; } + static int NoteVolumeFromMidi(mpt::const_byte_span midiData); + void LoadMetronomeSamples(); void UpdateMetronomeSamples(); void UpdateMetronomeVolume(); - static int ApplyVolumeRelatedSettings(const DWORD &dwParam1, const uint8 midivolume); - // static functions public: static CMainFrame *GetMainFrame() noexcept; @@ -380,7 +381,9 @@ void SetupMiscOptions(); void SetupPlayer(); - void SetupMidi(FlagSet d, UINT n); + void SetupMidi(FlagSet flags, UINT device); + void UpdateMidiFilters(); + HWND GetFollowSong() const; HWND GetFollowSong(const CModDoc *pDoc) const { return (pDoc == GetModPlaying()) ? GetFollowSong() : nullptr; } void ResetNotificationBuffer(); @@ -462,6 +465,7 @@ afx_msg void OnOpenMRUItem(UINT nId); afx_msg void OnUpdateMRUItem(CCmdUI *cmd); afx_msg LRESULT OnInvalidatePatterns(WPARAM, LPARAM); + afx_msg LRESULT OnMidiMsgReady(WPARAM, LPARAM); afx_msg LRESULT OnCustomKeyMsg(WPARAM, LPARAM); afx_msg void OnInternetUpdate(); afx_msg void OnUpdateAvailable(); Index: mptrack/MidiDeviceManager.cpp =================================================================== --- mptrack/MidiDeviceManager.cpp (nonexistent) +++ mptrack/MidiDeviceManager.cpp (working copy) @@ -0,0 +1,517 @@ +/* + * MidiDeviceManager.cpp + * --------------------- + * Purpose: MIDI device management, port sharing, and MIDI output thread handling + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" +#include "MidiDeviceManager.h" +#include "MidiInputCallback.h" +#include "Mptrack.h" +#include "TrackerSettings.h" +#include "../misc/mptClock.h" +#include "../soundlib/MIDIEvents.h" +#include "../src/openmpt/sounddevice/SoundDeviceManager.hpp" +#include "../src/openmpt/sounddevice/SoundDeviceUtilities.hpp" + +#include + +#include +#include +#include +#include + + +OPENMPT_NAMESPACE_BEGIN + + +static_assert(std::is_same_v); + + +struct MidiDeviceManager::SharedInputPort +{ + RtMidiIn rtMidiIn; + mpt::mutex callbackMutex; // Guards callbacks vector + std::vector callbacks; + std::vector sysexBuffer; + + bool HasReferences() const noexcept { return !callbacks.empty(); } +}; + + +struct MidiDeviceManager::SharedOutputPort +{ + RtMidiOut rtMidiOut; + std::thread outputThread; + mpt::mutex mutex; // Guards queue vector + std::vector queue; + std::condition_variable_any cv; + std::atomic running = false; + int refCount = 0; + + bool HasReferences() const noexcept { return refCount > 0; } +}; + + +MidiDeviceManager &MidiDeviceManager::Instance() +{ + static MidiDeviceManager instance; + return instance; +} + + +MidiDeviceManager::~MidiDeviceManager() +{ + MPT_ASSERT(m_openInputs.empty()); + MPT_ASSERT(m_openOutputs.empty()); +} + + +std::vector MidiDeviceManager::EnumeratePorts(RtMidi &rtMidi, bool isInput) +{ + std::vector ports; + try + { + const MidiPortID count = rtMidi.getPortCount(); + ports.reserve(count); + for(MidiPortID i = 0; i < count; i++) + { + try + { + std::string name = rtMidi.getPortName(i); + mpt::ustring friendlyName = theApp.GetFriendlyMIDIPortName(mpt::ToUnicode(mpt::Charset::UTF8, name), isInput, false); + ports.push_back({std::move(name), std::move(friendlyName), i}); + } catch(const RtMidiError &) + { + } + } + } catch(const RtMidiError &) + { + } + return ports; +} + + +std::vector MidiDeviceManager::EnumerateInputPorts() +{ + RtMidiIn rtMidi; + return EnumeratePorts(rtMidi, true); +} + + +std::vector MidiDeviceManager::EnumerateOutputPorts() +{ + RtMidiOut rtMidi; + return EnumeratePorts(rtMidi, false); +} + + +MidiPortID MidiDeviceManager::FindPort(RtMidi &rtMidi, const MidiPortInfo &port, bool isInput) +{ + if(port.id == kNoMidiDevice) + return port.id; + + MidiPortID candidate = port.id; + bool foundFriendly = false; + const MidiPortID numPorts = rtMidi.getPortCount(); + for(MidiPortID i = 0; i < numPorts; i++) + { + try + { + std::string portName = rtMidi.getPortName(i); + bool deviceNameMatches = (portName == port.name); + if(!port.friendlyName.empty() && port.friendlyName == theApp.GetFriendlyMIDIPortName(mpt::ToUnicode(mpt::Charset::UTF8, portName), isInput, false)) + { + candidate = i; + foundFriendly = true; + if(deviceNameMatches) + return candidate; + } + if(deviceNameMatches && !foundFriendly) + candidate = i; + } catch(const RtMidiError &) + { + } + } + return candidate; +} + + +MidiPortID MidiDeviceManager::FindInputPort(const MidiPortInfo &port) +{ + try + { + RtMidiIn rtMidi; + return FindPort(rtMidi, port, true); + } catch(const RtMidiError &) + { + return kNoMidiDevice; + } +} + + +MidiPortID MidiDeviceManager::FindOutputPort(const MidiPortInfo &port) +{ + try + { + RtMidiOut rtMidi; + return FindPort(rtMidi, port, false); + } catch(const RtMidiError &) + { + return kNoMidiDevice; + } +} + + +MidiPortID MidiDeviceManager::FindPort(const MidiPortInfo &port, bool asInputDevice) +{ + if(asInputDevice) + return FindInputPort(port); + else + return FindOutputPort(port); +} + + +MidiPortInfo MidiDeviceManager::GetPortInfo(const MidiInputHandle &handle) const +{ + SharedInputPort &port = handle.m_port; + auto it = std::find_if(m_openInputs.begin(), m_openInputs.end(), [&port](const auto &kv) { return &kv.second == &port; }); + if(it == m_openInputs.end()) + return {}; + + std::string name = port.rtMidiIn.getPortName(it->first); + mpt::ustring friendlyName = theApp.GetFriendlyMIDIPortName(mpt::ToUnicode(mpt::Charset::UTF8, name), false, false); + return {std::move(name), std::move(friendlyName), it->first}; +} + + +MidiPortInfo MidiDeviceManager::GetPortInfo(const MidiOutputHandle &handle) const +{ + SharedOutputPort &port = handle.m_port; + auto it = std::find_if(m_openOutputs.begin(), m_openOutputs.end(), [&port](const auto &kv) { return &kv.second == &port; }); + if(it == m_openOutputs.end()) + return {}; + + std::string name = port.rtMidiOut.getPortName(it->first); + mpt::ustring friendlyName = theApp.GetFriendlyMIDIPortName(mpt::ToUnicode(mpt::Charset::UTF8, name), true, false); + return {std::move(name), std::move(friendlyName), it->first}; +} + + + +// RtMidi input callback that dispatches to all registered listeners +void MidiDeviceManager::InputCallback(double /*deltatime*/, std::vector *message, void *userData) +{ + if(!message || message->empty() || !userData) + return; + + auto &port = *static_cast(userData); + const auto span = mpt::byte_cast(mpt::as_span(*message)); + + std::vector callbacks; + { + // Make a copy, in case the user opens this input device in another MIDI I/O plugin instance at the same time + std::lock_guard lock{port.callbackMutex}; + callbacks = port.callbacks; + } + + if(!port.sysexBuffer.empty() && !(message->front() & 0x80)) + { + // Continued SysEx message + port.sysexBuffer.insert(port.sysexBuffer.end(), span.begin(), span.end()); + if(message->back() == MIDIEvents::sysExEnd) + { + for(auto *callback : callbacks) + callback->OnMidiMessage(port.sysexBuffer); + port.sysexBuffer.clear(); + } + return; + } else if(message->front() == MIDIEvents::sysExStart && message->back() != MIDIEvents::sysExEnd) + { + // Start of SysEx but not complete yet (PortAudio backends are inconsistent in whether they buffer incomplete messages or not) + port.sysexBuffer.assign(span.begin(), span.end()); + return; + } + + for(auto *callback : callbacks) + callback->OnMidiMessage(span); +} + + +std::unique_ptr MidiDeviceManager::OpenInput(MidiPortID port, IMidiInputCallback *callback) +{ + if(port == kNoMidiDevice || !callback) + return nullptr; + + mpt::lock_guard lock{m_managerMutex}; + + auto &shared = m_openInputs[port]; + if(shared.rtMidiIn.isPortOpen()) + { + // Port already open - add this callback as a listener + mpt::lock_guard callbackLock{shared.callbackMutex}; + shared.callbacks.push_back(callback); + } else + { + // Need to open the port + try + { + shared.sysexBuffer.clear(); + shared.rtMidiIn.openPort(port); + shared.rtMidiIn.ignoreTypes(false, true, true); // Accept SysEx, ignore timing / active sensing + shared.callbacks.push_back(callback); + shared.rtMidiIn.setCallback(InputCallback, &shared); + } catch(const RtMidiError &) + { + m_openInputs.erase(port); + return nullptr; + } + } + + return std::unique_ptr(new MidiInputHandle{shared, *callback}); +} + + +void MidiDeviceManager::CloseInput(MidiInputHandle &handle) +{ + mpt::lock_guard lock{m_managerMutex}; + SharedInputPort &port = handle.m_port; + + // Remove this specific callback + auto &cbs = port.callbacks; + cbs.erase(std::remove(cbs.begin(), cbs.end(), &handle.m_callback), cbs.end()); + + if(port.HasReferences()) + return; + + // This was the last listener, close the port + try + { + port.rtMidiIn.closePort(); + } catch(const RtMidiError &) + { + } + auto it = std::find_if(m_openInputs.begin(), m_openInputs.end(), [&port](const auto &kv) { return &kv.second == &port; }); + if(it != m_openInputs.end()) + m_openInputs.erase(it); +} + + +MidiInputHandle::~MidiInputHandle() +{ + MidiDeviceManager::Instance().CloseInput(*this); +} + + +void MidiDeviceManager::OutputThreadFunc(SharedOutputPort &port) +{ + const auto *sdm = theApp.GetSoundDevicesManager(); + const auto sysInfo = sdm->GetSysInfo(); + const auto appInfo = sdm->GetAppInfo(); + SoundDevice::CPriorityBooster priorityBooster{sysInfo, /*TrackerSettings::Instance().SoundBoostedThreadPriority*/ true, appInfo.BoostedThreadMMCSSClassVista, appInfo.BoostedThreadPriorityXP}; + //SoundDevice::CPeriodicWaker periodicWaker{m_WakeupInterval}; + Util::MultimediaClock clock; + clock.SetResolution(1); + + static constexpr double MIN_WAIT_TIME_MS = 1.0; + + auto waitTimeMs = [this, &clock](int64 eventStreamFrame) + { + SyncPoint sp = m_syncPoint; + // Sync point not valid yet? Wait some more. + if(sp.sampleRate == 0 || sp.speed <= 0.0) + return MIN_WAIT_TIME_MS; + + const double srFactor = sp.speed * sp.sampleRate; + int64 actualCurrentFrame = mpt::saturate_round( + sp.streamFrames + + (static_cast(static_cast(clock.NowNanoseconds() - sp.systemTimestampNs)) * srFactor * (1.0 / 1'000'000'000.0))); + + double frameDelta = static_cast(eventStreamFrame - actualCurrentFrame); + double waitMs = 1000.0 * frameDelta / srFactor; + return std::min(waitMs, 10.0); // In case of large buffer, only wait a smaller amount of time to decrease chance of overshooting + }; + + std::vector localQueue; + + while(true) + { + { + std::unique_lock lock{port.mutex}; + port.cv.wait(lock, [&]() { return !port.queue.empty() || !port.running; }); + if(!port.running && port.queue.empty()) + break; + localQueue.assign(std::make_move_iterator(port.queue.begin()), std::make_move_iterator(port.queue.end())); + port.queue.clear(); + // If two I/O plugin instances with different latency settings use the same output device, the timestamps are not going to be ordered correctly + std::stable_sort(localQueue.begin(), localQueue.end()); + } + + for(size_t i = 0; i < localQueue.size(); i++) + { + double waitMs = waitTimeMs(localQueue[i].m_streamFrame); + + // Wait until it's time to send this message (unless we're shutting down) + while(waitMs > 0.0 && port.running) + { + if(waitMs >= MIN_WAIT_TIME_MS) + { + //OutputDebugString(MPT_CFORMAT("sleep {}\n")(waitMs)); + // Sleep until ~1ms before target, then spin-wait for more precise timing + std::unique_lock lock{port.mutex}; + port.cv.wait_for(lock, std::chrono::nanoseconds{static_cast((waitMs - 1.0) * 1'000'000.0)}); + if(!port.queue.empty()) + { + // Move any newly queued messages into our local queue + localQueue.insert(localQueue.end(), std::make_move_iterator(port.queue.begin()), std::make_move_iterator(port.queue.end())); + port.queue.clear(); + // If two I/O plugin instances with different latency settings use the same output device, the timestamps are not going to be ordered correctly. + std::stable_sort(localQueue.begin() + i, localQueue.end()); + } + } + waitMs = waitTimeMs(localQueue[i].m_streamFrame); + } + + // If we're draining messages, don't send any new note-on messages + if(MIDIEvents::GetTypeFromEvent(localQueue[i]) == MIDIEvents::evNoteOn && !port.running) + continue; + + try + { + port.rtMidiOut.sendMessage(mpt::byte_cast(localQueue[i].m_message), localQueue[i].m_size); + } catch(const RtMidiError &) + { + } + } + } + + clock.SetResolution(0); +} + + +std::unique_ptr MidiDeviceManager::OpenOutput(MidiPortID port) +{ + if(port == kNoMidiDevice) + return nullptr; + + mpt::lock_guard lock{m_managerMutex}; + + auto &shared = m_openOutputs[port]; + if(shared.rtMidiOut.isPortOpen()) + { + // Port already open, share it + } else + { + // Need to open the port and start the output thread + try + { + shared.queue.reserve(256); + shared.rtMidiOut.openPort(port); + shared.running = true; + shared.outputThread = std::thread([this, &shared]() { OutputThreadFunc(shared); }); + } catch(const RtMidiError &) + { + m_openOutputs.erase(port); + return nullptr; + } + } + shared.refCount++; + + return std::unique_ptr(new MidiOutputHandle{shared}); +} + + +void MidiDeviceManager::CloseOutput(MidiOutputHandle &handle) +{ + std::thread thread; + SharedOutputPort &port = handle.m_port; + + mpt::lock_guard lock{m_managerMutex}; + + port.refCount--; + if(port.HasReferences()) + return; + + // This was the last user of the port, signal thread to drain remaining messages and close the port + port.running = false; + port.cv.notify_all(); + + if(port.outputThread.joinable()) + port.outputThread.join(); + + try + { + port.rtMidiOut.closePort(); + } catch(const RtMidiError &) + { + } + auto it = std::find_if(m_openOutputs.begin(), m_openOutputs.end(), [&port](const auto &kv) { return &kv.second == &port; }); + if(it != m_openOutputs.end()) + m_openOutputs.erase(it); +} + + +MidiOutputHandle::~MidiOutputHandle() +{ + MidiDeviceManager::Instance().CloseOutput(*this); +} + + +void MidiOutputHandle::Send(mpt::const_byte_span data, int64 streamFrame) +{ + // TODO: make lock-free in the common case (ring buffer) + { + mpt::lock_guard lock{m_port.mutex}; + m_port.queue.emplace_back(streamFrame, data); + } + m_port.cv.notify_one(); +} + + +void MidiDeviceManager::UpdateSyncPoint(int64 syncPointStreamFrames, uint64 syncPointSystemTimestamp, double speed, uint32 sampleRate) +{ + m_syncPoint = {syncPointStreamFrames, syncPointSystemTimestamp, speed, sampleRate}; +} + + +bool MidiDeviceManager::CheckDeviceStatus(const MidiInputHandle &handle) +{ + // This is really dirty, but I don't think there is a neater way, short of rewriting RtMidi. + // WinMM does not report on its own if a MIDI device was disconnected. + // One possibility is to call any API such as midiInGetNumDevs, which will then trigger a MIM_CLOSE message. + // But RtMidi does not handle MIM_CLOSE, and even if it did, it has no callback mechanism to notify us of such an event. + // So our only other option really is to try to do something non-destructive with the MIDI In handle and see if it's still valid. + // midiInGetID is not enough (it will return MMSYSERR_NOERROR), so midiInMessage is our next-best bet. + class MidiApiEx : public MidiApi + { + public: + bool IsValid() + { + if(isPortOpen()) + return false; + if(getCurrentApi() != RtMidi::WINDOWS_MM) + return true; + HMIDIIN handle = *static_cast(apiData_); // First member of WinMidiData + ULONG size = 0; + return midiInMessage(handle, 0x813 /*DRV_QUERYDEVICEINTERFACESIZE*/, reinterpret_cast(&size), 0) != MMSYSERR_NODRIVER; + } + }; + + class RtMidiInEx : public RtMidiIn + { + public: + bool IsValid() const + { + return static_cast(rtapi_)->IsValid(); + } + }; + + return static_cast(handle.m_port.rtMidiIn).IsValid(); +} + + +OPENMPT_NAMESPACE_END Property changes on: mptrack/MidiDeviceManager.cpp ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/x-c++src \ No newline at end of property Index: mptrack/MidiDeviceManager.h =================================================================== --- mptrack/MidiDeviceManager.h (nonexistent) +++ mptrack/MidiDeviceManager.h (working copy) @@ -0,0 +1,220 @@ +/* + * MidiDeviceManager.h + * ------------------- + * Purpose: MIDI device management, port sharing, and MIDI output thread handling + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#pragma once + +#include "openmpt/all/BuildSettings.hpp" +#include "mpt/mutex/mutex.hpp" + +#include +#include +#include +#include +#include + + +OPENMPT_NAMESPACE_BEGIN + + +using MidiPortID = unsigned int; +constexpr MidiPortID kNoMidiDevice = MidiPortID(-1); + +class RtMidi; +class IMidiInputCallback; +class MidiInputHandle; +class MidiOutputHandle; + + +// MIDI queue entry with small storage optimiziation. +// This optimiziation is going to be used for all messages that OpenMPT can send internally, +// but SysEx messages received from plugins or via MIDI in may be longer. +class MidiMessage +{ +public: + int64 m_streamFrame = 0; + size_t m_size = 0; + const std::byte *m_message = nullptr; + void *m_sender = nullptr; // For feedback prevention +protected: + std::array m_msgSmall; + +public: + MidiMessage(int64 streamFrame, const mpt::const_byte_span data, void *sender = nullptr) + : m_streamFrame{streamFrame} + , m_size{data.size()} + , m_sender{sender} + { + std::byte *message = m_msgSmall.data(); + if(m_size > m_msgSmall.size()) + message = new std::byte[m_size]; + std::memcpy(message, data.data(), m_size); + m_message = message; + } + + MidiMessage(const MidiMessage &) = delete; + MidiMessage &operator=(const MidiMessage &) = delete; + + operator mpt::const_byte_span() const { return mpt::as_span(m_message, m_size); } + + MidiMessage(MidiMessage &&other) noexcept + : m_streamFrame{other.m_streamFrame} + , m_size{other.m_size} + , m_message{other.m_message} + , m_sender{other.m_sender} + , m_msgSmall{other.m_msgSmall} + { + other.m_message = nullptr; + if(m_size <= m_msgSmall.size()) + m_message = m_msgSmall.data(); + } + + ~MidiMessage() + { + if(m_size > m_msgSmall.size()) + delete[] m_message; + } + + MidiMessage &operator=(MidiMessage &&other) noexcept + { + m_streamFrame = other.m_streamFrame; + m_size = other.m_size; + m_sender = other.m_sender; + m_message = (m_size <= m_msgSmall.size()) ? m_msgSmall.data() : other.m_message; + m_msgSmall = other.m_msgSmall; + other.m_message = nullptr; + return *this; + } + + bool operator<(const MidiMessage &rhs) const noexcept + { + return m_streamFrame < rhs.m_streamFrame; + } +}; + + +struct MidiPortInfo +{ + std::string name; // UTF-8, raw device name from RtMidi + mpt::ustring friendlyName; + MidiPortID id = kNoMidiDevice; +}; + + +class MidiDeviceManager +{ +public: + static MidiDeviceManager &Instance(); + + struct SyncPoint + { + int64 streamFrames = 0; + uint64 systemTimestampNs = 0; + double speed = 1.0; + uint32 sampleRate = 0; + }; + + // Enumerate available MIDI ports. + static std::vector EnumerateInputPorts(); + static std::vector EnumerateOutputPorts(); + + // Find a port by name/friendlyName, returning best-match ID. + // Returns kNoMidiDevice if no match is found. + static MidiPortID FindInputPort(const MidiPortInfo &port); + static MidiPortID FindOutputPort(const MidiPortInfo &port); + static MidiPortID FindPort(const MidiPortInfo &port, bool asInputDevice); + + MidiPortInfo GetPortInfo(const MidiInputHandle &handle) const; + MidiPortInfo GetPortInfo(const MidiOutputHandle &handle) const; + + // Open an input or output port. The same port can be opened multiple times. + // The port is closed by resetting the returned handle. + // Returns nullptr on failure. + std::unique_ptr OpenInput(MidiPortID port, IMidiInputCallback *callback); + std::unique_ptr OpenOutput(MidiPortID port); + + // Updates the current audio thread sync point to schedule MIDI output message timing against + void UpdateSyncPoint(int64 SyncPointStreamFrames, uint64 SyncPointSystemTimestamp, double speed, uint32 sampleRate); + void ResetSyncPoint() { m_syncPoint = SyncPoint{}; } + + // Check if a MIDI device has been disconnected + static bool CheckDeviceStatus(const MidiInputHandle &handle); + +private: + friend class MidiInputHandle; + friend class MidiOutputHandle; + + struct SharedInputPort; + struct SharedOutputPort; + + MidiDeviceManager() = default; + ~MidiDeviceManager(); + + // Internal helpers + static std::vector EnumeratePorts(RtMidi &rtMidi, bool isInput); + static MidiPortID FindPort(RtMidi &rtMidi, const MidiPortInfo &port, bool isInput); + + void CloseInput(MidiInputHandle &handle); + void CloseOutput(MidiOutputHandle &handle); + + static void InputCallback(double deltatime, std::vector *message, void *userData); + void OutputThreadFunc(SharedOutputPort &port); + + mpt::mutex m_managerMutex; + std::map m_openInputs; + std::map m_openOutputs; + + // TODO make this non-locking? + std::atomic m_syncPoint; +}; + + +// Handle to an open MIDI input port. Multiple handles can share the same physical port. +// Releasing the last handle for a port closes the physical device. +class MidiInputHandle final +{ + friend class MidiDeviceManager; + + MidiInputHandle(MidiDeviceManager::SharedInputPort &port, IMidiInputCallback &callback) + : m_port{port} + , m_callback{callback} + { + } + +public: + ~MidiInputHandle(); + +private: + MidiDeviceManager::SharedInputPort &m_port; + IMidiInputCallback &m_callback; +}; + + +// Handle to an open MIDI output port. Multiple handles can share the same physical port. +// Releasing the last handle for a port drains its messages and closes the device. +class MidiOutputHandle final +{ + friend class MidiDeviceManager; + + MidiOutputHandle(MidiDeviceManager::SharedOutputPort&port) + : m_port{port} + { + } + +public: + ~MidiOutputHandle(); + + void Send(mpt::const_byte_span data, int64 streamFrame); + +private: + MidiDeviceManager::SharedOutputPort &m_port; +}; + + +OPENMPT_NAMESPACE_END Property changes on: mptrack/MidiDeviceManager.h ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/x-chdr \ No newline at end of property Index: mptrack/MidiInputCallback.h =================================================================== --- mptrack/MidiInputCallback.h (nonexistent) +++ mptrack/MidiInputCallback.h (working copy) @@ -0,0 +1,49 @@ +/* + * MidiInputCallback.h + * ------------------- + * Purpose: Callback interface class for MIDI recording + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#pragma once + +#include "openmpt/all/BuildSettings.hpp" + + +OPENMPT_NAMESPACE_BEGIN + +// Callback interface to notify that a MIDI input callback has been destroyed +class IMidiInputCallbackDestroyedNotifySink +{ +public: + virtual ~IMidiInputCallbackDestroyedNotifySink() = default; + virtual void OnMidiInputCallbackDestroyed(void *) = 0; +}; + + +// Callback interface for receiving MIDI input messages +class IMidiInputCallback +{ +public: + virtual ~IMidiInputCallback() + { + if(m_notifySink) + m_notifySink->OnMidiInputCallbackDestroyed(this); + } + + virtual bool PreFilterMidiMessage(mpt::const_byte_span /*midiData*/) { return false; } + virtual void OnMidiMessage(mpt::const_byte_span midiData) = 0; + + void SetDestroyNotifySink(IMidiInputCallbackDestroyedNotifySink *notifySink) + { + m_notifySink = notifySink; + } + +private: + IMidiInputCallbackDestroyedNotifySink *m_notifySink = nullptr; +}; + +OPENMPT_NAMESPACE_END Property changes on: mptrack/MidiInputCallback.h ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/x-chdr \ No newline at end of property Index: mptrack/MIDIMapping.cpp =================================================================== --- mptrack/MIDIMapping.cpp (revision 25320) +++ mptrack/MIDIMapping.cpp (working copy) @@ -99,8 +99,10 @@ } -bool CMIDIMapper::OnMIDImsg(const DWORD midimsg, PLUGINDEX &mappedIndex, PlugParamIndex ¶mindex, uint16 ¶mval) +bool CMIDIMapper::OnMIDImsg(mpt::const_byte_span midimsg, PLUGINDEX &mappedIndex, PlugParamIndex ¶mindex, uint16 ¶mval) { + if(midimsg.empty()) + return false; const MIDIEvents::EventType eventType = MIDIEvents::GetTypeFromEvent(midimsg); const uint8 controller = MIDIEvents::GetDataByte1FromEvent(midimsg); const uint8 channel = MIDIEvents::GetChannelFromEvent(midimsg) & 0x7F; Index: mptrack/MIDIMapping.h =================================================================== --- mptrack/MIDIMapping.h (revision 25320) +++ mptrack/MIDIMapping.h (working copy) @@ -92,7 +92,7 @@ // - paramvalue to parameter value. // In case of multiple mappings, these get the values from the last mapping found. // Returns true if MIDI was 'captured' by some directive, false otherwise. - bool OnMIDImsg(const DWORD midimsg, PLUGINDEX &mappedIndex, PlugParamIndex ¶mindex, uint16 ¶mvalue); + bool OnMIDImsg(mpt::const_byte_span midimsg, PLUGINDEX &mappedIndex, PlugParamIndex ¶mindex, uint16 ¶mvalue); // Swaps the positions of two elements. void Swap(const size_t a, const size_t b); Index: mptrack/MIDIMappingDialog.cpp =================================================================== --- mptrack/MIDIMappingDialog.cpp (revision 25320) +++ mptrack/MIDIMappingDialog.cpp (working copy) @@ -32,13 +32,11 @@ , m_rMIDIMapper{m_sndFile.GetMIDIMapper()} { CMainFrame::GetInputHandler()->Bypass(true); - oldMIDIRecondWnd = CMainFrame::GetMainFrame()->GetMidiRecordWnd(); } CMIDIMappingDialog::~CMIDIMappingDialog() { - CMainFrame::GetMainFrame()->SetMidiRecordWnd(oldMIDIRecondWnd); CMainFrame::GetInputHandler()->Bypass(false); } @@ -68,16 +66,14 @@ ON_BN_CLICKED(IDC_BUTTON_ADD, &CMIDIMappingDialog::OnBnClickedButtonAdd) ON_BN_CLICKED(IDC_BUTTON_REPLACE, &CMIDIMappingDialog::OnBnClickedButtonReplace) ON_BN_CLICKED(IDC_BUTTON_REMOVE, &CMIDIMappingDialog::OnBnClickedButtonRemove) - ON_MESSAGE(WM_MOD_MIDIMSG, &CMIDIMappingDialog::OnMidiMsg) ON_NOTIFY(UDN_DELTAPOS, IDC_SPINMOVEMAPPING, &CMIDIMappingDialog::OnDeltaposSpinmovemapping) ON_BN_CLICKED(IDC_CHECK_PATRECORD, &CMIDIMappingDialog::OnBnClickedCheckPatRecord) END_MESSAGE_MAP() -LRESULT CMIDIMappingDialog::OnMidiMsg(WPARAM dwMidiDataParam, LPARAM) +void CMIDIMappingDialog::OnMidiMessage(mpt::const_byte_span midiData) { - uint32 midiData = static_cast(dwMidiDataParam); - if(IsDlgButtonChecked(IDC_CHECK_MIDILEARN)) + if(!midiData.empty() && IsDlgButtonChecked(IDC_CHECK_MIDILEARN)) { for(int i = 0; i < m_EventCBox.GetCount(); i++) { @@ -102,7 +98,6 @@ } } } - return 1; } @@ -157,7 +152,7 @@ GetDlgItem(IDC_CHECK_PATRECORD)->EnableWindow((m_sndFile.GetType() == MOD_TYPE_MPT) ? TRUE : FALSE); - CMainFrame::GetMainFrame()->SetMidiRecordWnd(GetSafeHwnd()); + CMainFrame::GetMainFrame()->SetMidiRecordCallback(this, MidiTransformers::None); CheckDlgButton(IDC_CHECK_MIDILEARN, BST_CHECKED); Index: mptrack/MIDIMappingDialog.h =================================================================== --- mptrack/MIDIMappingDialog.h (revision 25320) +++ mptrack/MIDIMappingDialog.h (working copy) @@ -13,6 +13,7 @@ #include "openmpt/all/BuildSettings.hpp" #include "CListCtrl.h" +#include "MidiInputCallback.h" #include "MIDIMapping.h" #include "ResizableDialog.h" #include "PluginComboBox.h" @@ -23,7 +24,7 @@ class CSoundFile; class CMIDIMapper; -class CMIDIMappingDialog : public ResizableDialog +class CMIDIMappingDialog : public ResizableDialog, public IMidiInputCallback { public: CMIDIMappingDirective m_Setting; @@ -31,7 +32,6 @@ protected: CSoundFile &m_sndFile; CMIDIMapper &m_rMIDIMapper; - HWND oldMIDIRecondWnd; // Dialog Data CComboBox m_ControllerCBox; @@ -60,6 +60,7 @@ BOOL OnInitDialog() override; void DoDataExchange(CDataExchange* pDX) override; // DDX/DDV support CString GetToolTipText(UINT id, HWND hwnd) const override; + void OnMidiMessage(mpt::const_byte_span midiData) override; DECLARE_MESSAGE_MAP() @@ -75,7 +76,6 @@ afx_msg void OnBnClickedButtonAdd(); afx_msg void OnBnClickedButtonReplace(); afx_msg void OnBnClickedButtonRemove(); - afx_msg LRESULT OnMidiMsg(WPARAM, LPARAM); afx_msg void OnDeltaposSpinmovemapping(NMHDR *pNMHDR, LRESULT *pResult); afx_msg void OnBnClickedCheckPatRecord(); }; Index: mptrack/MidiTransformer.cpp =================================================================== --- mptrack/MidiTransformer.cpp (nonexistent) +++ mptrack/MidiTransformer.cpp (working copy) @@ -0,0 +1,170 @@ +/* + * MidiTransformer.cpp + * ------------------- + * Purpose: Chainable MIDI input filters / transformers. + * Notes : Filters can be chained to transform MIDI data before it reaches the final consumer. + * Each filter receives MIDI messages, optionally transforms them, and forwards + * the result to the next callback in the chain. + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" +#include "MidiTransformer.h" +#include "Dlsbank.h" +#include "../soundlib/MIDIEvents.h" + + +OPENMPT_NAMESPACE_BEGIN + + +void MidiTransposeFilter::OnMidiMessage(mpt::const_byte_span midiData) +{ + if(midiData.size() != 3 || !m_transpose || IsBypassed()) + { + Forward(midiData); + return; + } + + MIDIEvents::EventType event = MIDIEvents::GetTypeFromEvent(midiData); + if((event == MIDIEvents::evNoteOn || event == MIDIEvents::evNoteOff) && MIDIEvents::GetChannelFromEvent(midiData) != 9) + { + int note = m_transpose + MIDIEvents::GetDataByte1FromEvent(midiData); + Limit(note, 0, 127); + std::array noteMsg; + std::copy(midiData.begin(), midiData.end(), noteMsg.begin()); + noteMsg[1] = mpt::byte_cast(static_cast(note)); + Forward(noteMsg); + return; + } + + Forward(midiData); +} + + +void MidiSustainFilter::OnMidiMessage(mpt::const_byte_span midiData) +{ + if(midiData.size() != 3) + { + Forward(midiData); + return; + } + + MIDIEvents::EventType event = MIDIEvents::GetTypeFromEvent(midiData); + const uint8 channel = MIDIEvents::GetChannelFromEvent(midiData); + + if(event == MIDIEvents::evNoteOn && MIDIEvents::GetDataByte2FromEvent(midiData) == 0) + event = MIDIEvents::evNoteOff; + + if(event == MIDIEvents::evNoteOff) + { + if(m_sustainActive[channel]) + { + // Buffer the note-off for later release + std::array noteOff; + std::copy(midiData.begin(), midiData.end(), noteOff.begin()); + m_sustainBuffer[channel].push_back(noteOff); + if(!IsBypassed()) + return; + } + } else if(event == MIDIEvents::evControllerChange) + { + const uint8 cc = MIDIEvents::GetDataByte1FromEvent(midiData); + const uint8 value = MIDIEvents::GetDataByte2FromEvent(midiData); + if(cc == MIDIEvents::MIDICC_HoldPedal_OnOff) + { + m_sustainActive[channel] = (value >= 0x40); + if(!m_sustainActive[channel]) + { + if(!IsBypassed()) + { + // Release all buffered note-offs + for(const auto &offEvent : m_sustainBuffer[channel]) + { + const auto span = mpt::as_span(offEvent); + Forward(span); + } + } + m_sustainBuffer[channel].clear(); + } + // Forward the CC itself as well + Forward(midiData); + return; + } + } + + Forward(midiData); +} + + +void MidiVolumeFilter::OnMidiMessage(mpt::const_byte_span midiData) +{ + if(midiData.size() != 3) + { + Forward(midiData); + return; + } + + const MIDIEvents::EventType event = MIDIEvents::GetTypeFromEvent(midiData); + const uint8 channel = MIDIEvents::GetChannelFromEvent(midiData); + + if(event == MIDIEvents::evControllerChange) + { + const uint8 cc = MIDIEvents::GetDataByte1FromEvent(midiData); + const uint8 value = MIDIEvents::GetDataByte2FromEvent(midiData); + if(cc == MIDIEvents::MIDICC_Volume_Coarse) + m_channelVolume[channel] = value; + } + + if(!IsBypassed() && event == MIDIEvents::evNoteOn && MIDIEvents::GetDataByte2FromEvent(midiData) > 0) + { + int scaledVol = ApplyVolumeSettings(midiData); + if(scaledVol >= 0) + { + // Clamp to MIDI velocity range and rebuild the message + uint8 newVelocity = static_cast(std::min((scaledVol + 1) / 2, 127)); + std::array noteOn; + std::copy(midiData.begin(), midiData.end(), noteOn.begin()); + noteOn[2] = mpt::byte_cast(newVelocity); + Forward(noteOn); + return; + } + } + + Forward(midiData); +} + + +int MidiVolumeFilter::ApplyVolumeSettings(mpt::const_byte_span midiData) const +{ + if(midiData.size() != 3) + return -1; + + const MIDIEvents::EventType event = MIDIEvents::GetTypeFromEvent(midiData); + const uint8 channel = MIDIEvents::GetChannelFromEvent(midiData); + int vol = MIDIEvents::GetDataByte2FromEvent(midiData); + if(event != MIDIEvents::evNoteOn && event != MIDIEvents::evNoteOff) + return -1; + + uint8 midiChannelVolume = m_channelVolume[channel]; + if(m_recordVelocity) + { + if(!m_applyChannelVolumeToVelocity) + midiChannelVolume = 127; + + vol = Util::muldivr_unsigned(CDLSBank::DLSMidiVolumeToLinear(vol), m_velocityAmplification * midiChannelVolume, 100 * 127 * 256); + Limit(vol, 1, 256); + } else + { + if(m_applyChannelVolumeToVelocity) + vol = midiChannelVolume * 2 + 2; + else + vol = -1; // Use default value + } + + return vol; +} + + +OPENMPT_NAMESPACE_END Property changes on: mptrack/MidiTransformer.cpp ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/x-c++src \ No newline at end of property Index: mptrack/MidiTransformer.h =================================================================== --- mptrack/MidiTransformer.h (nonexistent) +++ mptrack/MidiTransformer.h (working copy) @@ -0,0 +1,116 @@ +/* + * MidiTransformer.h + * ----------------- + * Purpose: Chainable MIDI input filters / transformers. + * Notes : Filters can be chained to transform MIDI data before it reaches the final consumer. + * Each filter receives MIDI messages, optionally transforms them, and forwards + * the result to the next callback in the chain. + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#pragma once + +#include "openmpt/all/BuildSettings.hpp" +#include "MidiInputCallback.h" + +#include +#include +#include + + +OPENMPT_NAMESPACE_BEGIN + + +enum class MidiTransformers : uint8 +{ + None = 0x00, + Volume = 0x01, + Sustain = 0x02, + Transpose = 0x04, + + All = Volume | Sustain | Transpose, + Default = Sustain | Transpose +}; +DECLARE_FLAGSET(MidiTransformers); + + +// Base class for all MIDI transformers +class MidiTransformer : public IMidiInputCallback +{ +public: + ~MidiTransformer() override = default; + + void SetNextCallback(IMidiInputCallback *next) { m_next = next; } + + void SetBypassed(bool bypassed) { m_bypassed = bypassed; } + bool IsBypassed() const { return m_bypassed; } + +protected: + // Forward a message to the next filter in the chain. + void Forward(mpt::const_byte_span midiData) + { + if(m_next && !midiData.empty()) + m_next->OnMidiMessage(midiData); + } + +private: + IMidiInputCallback *m_next = nullptr; + bool m_bypassed = false; +}; + + +// Transpose MIDI notes according to the configured transposition amount +class MidiTransposeFilter final : public MidiTransformer +{ +public: + void OnMidiMessage(mpt::const_byte_span midiData) override; + + void SetTranspose(int transpose) { m_transpose = transpose; } + +private: + int m_transpose = 0; +}; + + +// Buffer note-off messages while the sustain pedal (CC 64) is held, +// and release them when the pedal is released +class MidiSustainFilter final : public MidiTransformer +{ +public: + void OnMidiMessage(mpt::const_byte_span midiData) override; + +private: + std::array>, 16> m_sustainBuffer; + std::bitset<16> m_sustainActive; +}; + + +// Scale note-on velocity according to MIDI configuration +// (velocity toggle, velocity amplification, channel volume). +// When bypassed, the filter configuration can still be used by directly calling ApplyVolumeSettings() +class MidiVolumeFilter final : public MidiTransformer +{ +public: + MidiVolumeFilter() { m_channelVolume.fill(127); } + + void OnMidiMessage(mpt::const_byte_span midiData) override; + + void SetRecordVelocity(bool recordVelocity) { m_recordVelocity = recordVelocity; } + void SetApplyChannelVolumeToVelocity(bool applyChannelVolumeToVelocity) { m_applyChannelVolumeToVelocity = applyChannelVolumeToVelocity;} + void SetVelocityAmplification(uint16 velocityAmplification) { m_velocityAmplification = velocityAmplification; } + + // Scale and apply velocity, as well as channel volume according to configuration. + // Return volume in range [0, 256], or -1 for "use default". + int ApplyVolumeSettings(mpt::const_byte_span midiData) const; + +private: + std::array m_channelVolume; + uint16 m_velocityAmplification = 100; + bool m_recordVelocity = false; + bool m_applyChannelVolumeToVelocity = false; +}; + + +OPENMPT_NAMESPACE_END Property changes on: mptrack/MidiTransformer.h ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/x-chdr \ No newline at end of property Index: mptrack/mod2midi.cpp =================================================================== --- mptrack/mod2midi.cpp (revision 25320) +++ mptrack/mod2midi.cpp (working copy) @@ -315,8 +315,7 @@ { if(midiData.empty()) return false; - const uint8 type = mpt::byte_cast(midiData[0]); - if(type == MIDIEvents::sysExStart) + if(midiData[0] == std::byte{MIDIEvents::sysExStart}) { // SysEx WriteTicks(); @@ -328,10 +327,10 @@ // Note-On events go last to prevent early note-off in a situation like this: // ... ..|C-5 01 // C-5 01|=== .. - if(allowQueue && MIDIEvents::GetTypeFromEvent(type) == MIDIEvents::evNoteOn) + if(allowQueue && MIDIEvents::GetTypeFromEvent(midiData) == MIDIEvents::evNoteOn) { std::array midiDataArray; - MPT_ASSERT(midiData.size() <= sizeof(midiDataArray) && midiData.size() == MIDIEvents::GetEventLength(type)); + MPT_ASSERT(midiData.size() <= sizeof(midiDataArray) && midiData.size() == MIDIEvents::GetEventLength(midiData[0])); memcpy(midiDataArray.data(), midiData.data(), std::min(sizeof(midiDataArray), midiData.size())); m_queuedEvents.push_back(midiDataArray); return true; Index: mptrack/Moddoc.cpp =================================================================== --- mptrack/Moddoc.cpp (revision 25320) +++ mptrack/Moddoc.cpp (working copy) @@ -928,10 +928,8 @@ } -void CModDoc::ProcessMIDI(uint32 midiData, SAMPLEINDEX smp, INSTRUMENTINDEX ins, IMixPlugin *plugin, InputTargetContext ctx) +void CModDoc::ProcessMIDI(mpt::const_byte_span midiData, SAMPLEINDEX smp, INSTRUMENTINDEX ins, IMixPlugin *plugin) { - static uint8 midiVolume = 127; - MIDIEvents::EventType event = MIDIEvents::GetTypeFromEvent(midiData); const uint8 channel = MIDIEvents::GetChannelFromEvent(midiData); const uint8 midiByte1 = MIDIEvents::GetDataByte1FromEvent(midiData); @@ -942,35 +940,10 @@ if((event == MIDIEvents::evNoteOn) && !vol) event = MIDIEvents::evNoteOff; //Convert event to note-off if req'd - PLUGINDEX mappedIndex = 0; - PlugParamIndex paramIndex = 0; - uint16 paramValue = 0; - bool captured = m_SndFile.GetMIDIMapper().OnMIDImsg(midiData, mappedIndex, paramIndex, paramValue); - - // Handle MIDI messages assigned to shortcuts - CInputHandler *ih = CMainFrame::GetInputHandler(); - if(ih->HandleMIDIMessage(ctx, midiData) != kcNull - || ih->HandleMIDIMessage(kCtxAllContexts, midiData) != kcNull) - { - // Mapped to a command, no need to pass message on. - captured = true; - } - - if(captured) - { - // Event captured by MIDI mapping or shortcut, no need to pass message on. - return; - } - const bool validInstr = (ins > 0 && ins <= GetNumInstruments()), validSample = (smp > 0 && smp <= GetNumSamples()); switch(event) { case MIDIEvents::evNoteOff: - if(m_midiSustainActive[channel]) - { - m_midiSustainBuffer[channel].push_back(midiData); - return; - } if(validInstr || validSample) { LimitMax(note, NOTE_MAX); @@ -988,7 +961,7 @@ if(validInstr || validSample) { LimitMax(note, NOTE_MAX); - vol = CMainFrame::ApplyVolumeRelatedSettings(midiData, midiVolume); + vol = CMainFrame::NoteVolumeFromMidi(midiData); PlayNote(PlayNoteParam(note).Instrument(ins).Sample(smp).Volume(vol).CheckNNA(m_midiPlayingNotes[channel]), &m_noteChannel); return; } else if(plugin != nullptr) @@ -997,27 +970,6 @@ } break; - case MIDIEvents::evControllerChange: - switch(midiByte1) - { - case MIDIEvents::MIDICC_Volume_Coarse: - midiVolume = midiByte2; - break; - case MIDIEvents::MIDICC_HoldPedal_OnOff: - m_midiSustainActive[channel] = (midiByte2 >= 0x40); - if(!m_midiSustainActive[channel]) - { - // Release all notes - for(const auto offEvent : m_midiSustainBuffer[channel]) - { - ProcessMIDI(offEvent, 0, ins, plugin, ctx); - } - m_midiSustainBuffer[channel].clear(); - } - break; - } - break; - case MIDIEvents::evPitchBend: for(size_t n = NOTE_MIN; n <= NOTE_MAX; n++) { @@ -1037,7 +989,7 @@ { plugin->MidiSend(midiData); // Sending midi may modify the plug. For now, if MIDI data is not active sensing or aftertouch messages, set modified. - if(midiData != MIDIEvents::System(MIDIEvents::sysActiveSense) + if(!mpt::span_elements_equal(midiData, MIDIEvents::System(MIDIEvents::sysActiveSense)) && event != MIDIEvents::evPolyAftertouch && event != MIDIEvents::evChannelAftertouch && event != MIDIEvents::evPitchBend && m_SndFile.GetModSpecifications().supportsPlugins) Index: mptrack/Moddoc.h =================================================================== --- mptrack/Moddoc.h (revision 25320) +++ mptrack/Moddoc.h (working copy) @@ -43,9 +43,7 @@ bool octaveLink = false; // apply octaveModifier }; -enum InputTargetContext : int8; - struct LogEntry { LogLevel level; @@ -98,7 +96,7 @@ PlayNoteParam& Offset(SmpLength sampleOffset) { m_sampleOffset = sampleOffset; return *this; } PlayNoteParam& Volume(int32 volume) { m_volume = volume; return *this; } - PlayNoteParam& Panning(int32 panning) { m_panning= panning; return *this; } + PlayNoteParam& Panning(int32 panning) { m_panning = panning; return *this; } PlayNoteParam& Sample(SAMPLEINDEX sample) { m_sample = sample; return *this; } PlayNoteParam& Instrument(INSTRUMENTINDEX instr) { m_instr = instr; return *this; } PlayNoteParam& Channel(CHANNELINDEX channel) { m_currentChannel = channel; return *this; } @@ -148,10 +146,7 @@ bool m_bHasValidPath = false; //becomes true if document is loaded or saved. protected: - // Note-off event buffer for MIDI sustain pedal - std::array, 16> m_midiSustainBuffer; std::array, 16> m_midiPlayingNotes; - std::bitset<16> m_midiSustainActive; std::vector m_multiRecordGroup; @@ -257,7 +252,7 @@ bool RemoveSample(SAMPLEINDEX nSmp); bool RemoveInstrument(INSTRUMENTINDEX nIns); - void ProcessMIDI(uint32 midiData, SAMPLEINDEX smp, INSTRUMENTINDEX ins, IMixPlugin *plugin, InputTargetContext ctx); + void ProcessMIDI(mpt::const_byte_span midiData, SAMPLEINDEX smp, INSTRUMENTINDEX ins, IMixPlugin *plugin); CHANNELINDEX PlayNote(PlayNoteParam ¶ms, NoteToChannelMap *noteChannel = nullptr); bool NoteOff(UINT note, bool fade = false, INSTRUMENTINDEX ins = INSTRUMENTINDEX_INVALID, CHANNELINDEX currentChn = CHANNELINDEX_INVALID); void CheckNNA(ModCommand::NOTE note, INSTRUMENTINDEX ins, std::bitset<128> &playingNotes); Index: mptrack/Mpdlgs.cpp =================================================================== --- mptrack/Mpdlgs.cpp (revision 25320) +++ mptrack/Mpdlgs.cpp (working copy) @@ -14,6 +14,7 @@ #include "dlg_misc.h" #include "ImageLists.h" #include "Mainfrm.h" +#include "MidiDeviceManager.h" #include "Moddoc.h" #include "Mptrack.h" #include "Reporting.h" @@ -1755,6 +1756,9 @@ } +CMidiSetupDlg::~CMidiSetupDlg() {} + + BOOL CMidiSetupDlg::OnInitDialog() { CPropertyPage::OnInitDialog(); @@ -1856,19 +1860,15 @@ { m_InputDevice.SetRedraw(FALSE); m_InputDevice.ResetContent(); - UINT ndevs = midiInGetNumDevs(); - for(UINT i = 0; i < ndevs; i++) + m_midiInPorts = MidiDeviceManager::Instance().EnumerateInputPorts(); + for(const MidiPortInfo &port : m_midiInPorts) { - MIDIINCAPS mic; - mic.szPname[0] = 0; - if(midiInGetDevCaps(i, &mic, sizeof(mic)) == MMSYSERR_NOERROR) + CString displayName = theApp.GetFriendlyMIDIPortName(mpt::ToCString(mpt::Charset::UTF8, port.name), true); + int item = m_InputDevice.AddString(displayName); + m_InputDevice.SetItemData(item, port.id); + if(port.id == currentDevice) { - int item = m_InputDevice.AddString(theApp.GetFriendlyMIDIPortName(mpt::ToCString(mpt::String::ReadWinBuf(mic.szPname)), true)); - m_InputDevice.SetItemData(item, i); - if(i == currentDevice) - { - m_InputDevice.SetCurSel(item); - } + m_InputDevice.SetCurSel(item); } } m_InputDevice.SetRedraw(TRUE); @@ -1881,12 +1881,17 @@ int n = m_InputDevice.GetCurSel(); if(n >= 0) { - UINT device = static_cast(m_InputDevice.GetItemData(n)); - MIDIINCAPS mic; - mic.szPname[0] = 0; - midiInGetDevCaps(device, &mic, sizeof(mic)); - CString name = mic.szPname; - CString friendlyName = theApp.GetSettings().Read(U_("MIDI Input Ports"), mpt::ToUnicode(name), name); + MidiPortID device = static_cast(m_InputDevice.GetItemData(n)); + CString name, friendlyName; + for(const MidiPortInfo &port : m_midiInPorts) + { + if(port.id == device) + { + name = mpt::ToCString(mpt::Charset::UTF8, port.name); + friendlyName = mpt::ToCString(port.friendlyName); + break; + } + } CInputDlg dlg(this, _T("New name for ") + name + _T(":"), friendlyName); if(dlg.DoModal() == IDOK) { Index: mptrack/Mpdlgs.h =================================================================== --- mptrack/Mpdlgs.h (revision 25320) +++ mptrack/Mpdlgs.h (working copy) @@ -21,6 +21,7 @@ class CSoundFile; class CMainFrame; +struct MidiPortInfo; #define NUM_CHANNELCOMBOBOXES 4 @@ -205,6 +206,7 @@ public: CMidiSetupDlg(FlagSet flags, UINT device); + ~CMidiSetupDlg(); protected: BOOL OnInitDialog() override; @@ -220,6 +222,7 @@ CSpinButtonCtrl m_SpinSpd, m_SpinPat, m_SpinAmp; CComboBox m_InputDevice, m_ATBehaviour, m_Quantize, m_ContinueMode, m_RecordPitchBend; AccessibleEdit m_editAmp; + std::vector m_midiInPorts; }; Index: mptrack/Mpt_midi.cpp =================================================================== --- mptrack/Mpt_midi.cpp (revision 25320) +++ mptrack/Mpt_midi.cpp (nonexistent) @@ -1,216 +0,0 @@ -/* - * MPT_MIDI.cpp - * ------------ - * Purpose: MIDI Input handling code. - * Notes : (currently none) - * Authors: OpenMPT Devs - * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. - */ - - -#include "stdafx.h" -#include "Dlsbank.h" -#include "InputHandler.h" -#include "Mainfrm.h" -#include "resource.h" -#include "TrackerSettings.h" -#include "WindowMessages.h" -#include "../soundlib/MIDIEvents.h" - -#include - - -OPENMPT_NAMESPACE_BEGIN - - -#ifdef MPT_ALL_LOGGING -#define MPTMIDI_RECORDLOG -#endif - -//Get Midi message(dwParam1), apply MIDI settings having effect on volume, and return -//the volume value [0, 256]. In addition value -1 is used as 'use default value'-indicator. -int CMainFrame::ApplyVolumeRelatedSettings(const DWORD &dwParam1, uint8 midiChannelVolume) -{ - int nVol = MIDIEvents::GetDataByte2FromEvent(dwParam1); - const FlagSet midiSetup = TrackerSettings::Instance().midiSetup; - if(midiSetup[MidiSetup::RecordVelocity]) - { - if(!midiSetup[MidiSetup::ApplyChannelVolumeToVelocity]) - midiChannelVolume = 127; - nVol = Util::muldivr_unsigned(CDLSBank::DLSMidiVolumeToLinear(nVol), TrackerSettings::Instance().midiVelocityAmp * midiChannelVolume, 100 * 127 * 256); - Limit(nVol, 1, 256); - } else - { - // Case: No velocity record. - if(midiSetup[MidiSetup::ApplyChannelVolumeToVelocity]) - nVol = (midiChannelVolume + 1) * 2; - else //Use default volume - nVol = -1; - } - - return nVol; -} - - -void ApplyTransposeKeyboardSetting(CMainFrame &rMainFrm, uint32 &midiMsg) -{ - const FlagSet midiSetup = TrackerSettings::Instance().midiSetup; - if(midiSetup[MidiSetup::TransposeKeyboard] - && (MIDIEvents::GetChannelFromEvent(midiMsg) != 9)) - { - int nTranspose = rMainFrm.GetBaseOctave() - 4; - if (nTranspose) - { - int note = MIDIEvents::GetDataByte1FromEvent(midiMsg); - if (note < 0x80) - { - note += nTranspose * 12; - Limit(note, 0, NOTE_MAX - NOTE_MIN); - - midiMsg &= 0xffff00ff; - - midiMsg |= (note << 8); - } - } - } -} - - -///////////////////////////////////////////////////////////////////////////// -// MMSYSTEM Midi Record - -void CALLBACK MidiInCallBack(HMIDIIN inHandle, UINT wMsg, DWORD_PTR instance, DWORD_PTR midiMsg, DWORD_PTR timestamp) -{ - CMainFrame *pMainFrm = reinterpret_cast(instance); - if(!pMainFrm) - return; - -#ifdef MPTMIDI_RECORDLOG - DWORD dwMidiStatus = midiMsg & 0xFF; - DWORD dwMidiByte1 = (midiMsg >> 8) & 0xFF; - DWORD dwMidiByte2 = (midiMsg >> 16) & 0xFF; - MPT_LOG_GLOBAL(LogDebug, "MIDI", MPT_UFORMAT("time={}ms status={} data={}.{}")(mpt::ufmt::dec(timestamp), mpt::ufmt::HEX0<2>(dwMidiStatus), mpt::ufmt::HEX0<2>(dwMidiByte1), mpt::ufmt::HEX0<2>(dwMidiByte2))); -#endif - - HWND hWndMidi = pMainFrm->GetMidiRecordWnd(); - if(wMsg == MIM_DATA || wMsg == MIM_MOREDATA) - { - uint32 data = static_cast(midiMsg); - if(::IsWindow(hWndMidi)) - { - switch(MIDIEvents::GetTypeFromEvent(data)) - { - case MIDIEvents::evNoteOff: // Note Off - case MIDIEvents::evNoteOn: // Note On - ApplyTransposeKeyboardSetting(*pMainFrm, data); - [[fallthrough]]; - default: - if(::PostMessage(hWndMidi, WM_MOD_MIDIMSG, data, timestamp)) - return; // Message has been handled - break; - } - } - // Pass MIDI to keyboard handler - CMainFrame::GetInputHandler()->HandleMIDIMessage(kCtxAllContexts, data); - } else if(wMsg == MIM_LONGDATA) - { - // SysEx - if(MIDIHDR &sysex = *reinterpret_cast(midiMsg); sysex.dwBytesRecorded) - { - std::lock_guard guard{pMainFrm->midiInData.dataMutex}; - if(auto callback = pMainFrm->GetMidiSysexCallback()) - callback(mpt::const_byte_span{mpt::byte_cast(sysex.lpData), sysex.dwBytesRecorded}); - midiInAddBuffer(inHandle, &sysex, sizeof(sysex)); - } - } else if(wMsg == MIM_CLOSE) - { - // midiInClose will trigger this, but also disconnecting a USB MIDI device (although delayed, seems to be coupled to calling something like midiInGetNumDevs). - // In the latter case, we need to inform the UI. - if(pMainFrm->midiInData.inHandle != nullptr) - { - pMainFrm->SendMessage(WM_COMMAND, ID_MIDI_RECORD); - } - } -} - - -bool CMainFrame::midiOpenDevice(bool showSettings) -{ - if (midiInData.inHandle) return true; - - if (midiInOpen(&midiInData.inHandle, TrackerSettings::Instance().GetCurrentMIDIDevice(), reinterpret_cast(MidiInCallBack), reinterpret_cast(this), CALLBACK_FUNCTION) != MMSYSERR_NOERROR) - { - midiInData.inHandle = nullptr; - - // Show MIDI configuration on fail. - if(showSettings) - { - CMainFrame::m_nLastOptionsPage = OPTIONS_PAGE_MIDI; - CMainFrame::GetMainFrame()->OnViewOptions(); - } - - // Let's see if the user updated the settings. - if(midiInOpen(&midiInData.inHandle, TrackerSettings::Instance().GetCurrentMIDIDevice(), reinterpret_cast(MidiInCallBack), reinterpret_cast(this), CALLBACK_FUNCTION) != MMSYSERR_NOERROR) - { - midiInData.inHandle = nullptr; - return false; - } - } - - midiInData.sysexBuffers.resize(16); - for(auto &buffer : midiInData.sysexBuffers) - { - buffer.data.resize(4096); - buffer.header.lpData = mpt::byte_cast(buffer.data.data()); - buffer.header.dwBufferLength = mpt::saturate_cast(buffer.data.size()); - buffer.header.dwFlags = 0; - - midiInPrepareHeader(midiInData.inHandle, &buffer.header, sizeof(buffer.header)); - midiInAddBuffer(midiInData.inHandle, &buffer.header, sizeof(buffer.header)); - } - - midiInStart(midiInData.inHandle); - return true; -} - - -void CMainFrame::midiCloseDevice() -{ - if(midiInData.inHandle) - { - // Prevent infinite loop in MIM_CLOSE - auto handle = midiInData.inHandle; - midiInData.inHandle = nullptr; - - std::lock_guard guard{midiInData.dataMutex}; - midiInReset(handle); - midiInStop(handle); - - for(auto &buffer : midiInData.sysexBuffers) - { - midiInUnprepareHeader(handle, &buffer.header, sizeof(buffer.header)); - } - midiInClose(handle); - } -} - - -void CMainFrame::OnMidiRecord() -{ - if(midiInData.inHandle) - { - midiCloseDevice(); - } else - { - midiOpenDevice(); - } -} - - -void CMainFrame::OnUpdateMidiRecord(CCmdUI *pCmdUI) -{ - if (pCmdUI) pCmdUI->SetCheck((midiInData.inHandle) ? TRUE : FALSE); -} - - -OPENMPT_NAMESPACE_END Property changes on: mptrack/Mpt_midi.cpp ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Deleted: svn:keywords ## -1 +0,0 ## -Author Date Id Revision \ No newline at end of property Deleted: svn:mime-type ## -1 +0,0 ## -text/x-c++src \ No newline at end of property Index: mptrack/plugins/MidiInOut.cpp =================================================================== --- mptrack/plugins/MidiInOut.cpp (revision 25320) +++ mptrack/plugins/MidiInOut.cpp (working copy) @@ -16,9 +16,6 @@ #include "../Reporting.h" #include #include -#ifdef MODPLUG_TRACKER -#include "../Mptrack.h" -#endif #include "mpt/io/io.hpp" #include "mpt/io/io_stdstream.hpp" @@ -28,24 +25,18 @@ IMixPlugin* MidiInOut::Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN &mixStruct) { - try - { - return new (std::nothrow) MidiInOut(factory, sndFile, mixStruct); - } catch(RtMidiError &) - { - return nullptr; - } + return new (std::nothrow) MidiInOut(factory, sndFile, mixStruct); } MidiInOut::MidiInOut(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN &mixStruct) : IMidiPlugin(factory, sndFile, mixStruct) - , m_inputDevice(m_midiIn) - , m_outputDevice(m_midiOut) #ifdef MODPLUG_TRACKER , m_programName(_T("Default")) #endif // MODPLUG_TRACKER { + m_inputDevice.name = ""; + m_outputDevice.name = ""; m_mixBuffer.Initialize(2, 2); } @@ -59,7 +50,7 @@ uint32 MidiInOut::GetLatency() const { // There is only a latency if the user-provided latency value is greater than the negative output latency. - return mpt::saturate_round(std::min(0.0, m_latency + (m_outputDevice.index == kInternalDevice ? 0.0 : GetOutputLatency())) * m_SndFile.GetSampleRate()); + return mpt::saturate_round(std::min(0.0, m_latency + (m_outputDevice.id == kInternalDevice ? 0.0 : GetOutputLatency())) * m_SndFile.GetSampleRate()); } @@ -88,22 +79,22 @@ constexpr auto ParameterToDeviceID = [](float value) { - return static_cast(value * 65536.0f) - 1; + return static_cast(value * 65536.0f) - 1; }; - m_inputDevice.index = ParameterToDeviceID(memFile.ReadFloatLE()); - m_outputDevice.index = ParameterToDeviceID(memFile.ReadFloatLE()); + m_inputDevice.id = ParameterToDeviceID(memFile.ReadFloatLE()); + m_outputDevice.id = ParameterToDeviceID(memFile.ReadFloatLE()); } else { SetChunk(mpt::as_span(m_pMixStruct->pluginData), false); } - OpenDevice(m_inputDevice.index, true); - OpenDevice(m_outputDevice.index, false); + OpenDevice(MidiDeviceManager::FindInputPort(m_inputDevice), true); + OpenDevice(MidiDeviceManager::FindOutputPort(m_outputDevice), false); // Update selection in editor MidiInOutEditor *editor = dynamic_cast(GetEditor()); if(editor != nullptr) { - editor->SetCurrentDevice(true, m_inputDevice.index); - editor->SetCurrentDevice(false, m_outputDevice.index); + editor->SetCurrentDevice(true, m_inputDevice.id); + editor->SetCurrentDevice(false, m_outputDevice.id); } } @@ -123,9 +114,8 @@ { const std::string programName8 = mpt::ToCharset(mpt::Charset::UTF8, m_programName); uint32 flags = kLatencyCompensation | kLatencyPresent | (m_sendTimingInfo ? 0 : kIgnoreTiming); -#ifdef MODPLUG_TRACKER - const std::string inFriendlyName = (m_inputDevice.index != MidiDevice::NO_MIDI_DEVICE && !m_inputDevice.friendlyName.empty()) ? mpt::ToCharset(mpt::Charset::UTF8, m_inputDevice.friendlyName) : m_inputDevice.name; - const std::string outFriendlyName = (m_outputDevice.index != MidiDevice::NO_MIDI_DEVICE && !m_outputDevice.friendlyName.empty()) ? mpt::ToCharset(mpt::Charset::UTF8, m_outputDevice.friendlyName) : m_outputDevice.name; + const std::string inFriendlyName = (m_inputDevice.id != kNoMidiDevice && !m_inputDevice.friendlyName.empty()) ? mpt::ToCharset(mpt::Charset::UTF8, m_inputDevice.friendlyName) : m_inputDevice.name; + const std::string outFriendlyName = (m_outputDevice.id != kNoMidiDevice && !m_outputDevice.friendlyName.empty()) ? mpt::ToCharset(mpt::Charset::UTF8, m_outputDevice.friendlyName) : m_outputDevice.name; if(inFriendlyName != m_inputDevice.name) { flags |= kFriendlyInputName; @@ -134,7 +124,6 @@ { flags |= kFriendlyOutputName; } -#endif if(!m_initialMidiDump.empty()) { flags |= kMacrosPresent; @@ -157,9 +146,9 @@ mpt::IO::WriteIntLE< int32>(s, GetVersion()); mpt::IO::WriteIntLE(s, 1); // Number of programs mpt::IO::WriteIntLE(s, static_cast(programName8.size())); - mpt::IO::WriteIntLE(s, m_inputDevice.index); + mpt::IO::WriteIntLE(s, m_inputDevice.id); mpt::IO::WriteIntLE(s, static_cast(m_inputDevice.name.size())); - mpt::IO::WriteIntLE(s, m_outputDevice.index); + mpt::IO::WriteIntLE(s, m_outputDevice.id); mpt::IO::WriteIntLE(s, static_cast(m_outputDevice.name.size())); mpt::IO::WriteIntLE(s, flags); mpt::IO::WriteRaw(s, programName8.c_str(), programName8.size()); @@ -166,7 +155,6 @@ mpt::IO::WriteRaw(s, m_inputDevice.name.c_str(), m_inputDevice.name.size()); mpt::IO::WriteRaw(s, m_outputDevice.name.c_str(), m_outputDevice.name.size()); mpt::IO::WriteIntLE(s, IEEE754binary64LE(m_latency).GetInt64()); -#ifdef MODPLUG_TRACKER if(flags & kFriendlyInputName) { mpt::IO::WriteIntLE(s, static_cast(inFriendlyName.size())); @@ -177,7 +165,6 @@ mpt::IO::WriteIntLE(s, static_cast(outFriendlyName.size())); mpt::IO::WriteRaw(s, outFriendlyName.c_str(), outFriendlyName.size()); } -#endif if(flags & kMacrosPresent) { if(!m_initialMidiDump.empty()) @@ -204,44 +191,6 @@ } -// Try to match a port name against stored name or friendly name (preferred) -static MidiDevice::ID FindPort(MidiDevice::ID id, unsigned int numPorts, const std::string &name, const mpt::ustring &friendlyName, MidiDevice &midiDevice, bool isInput) -{ - if(id == MidiDevice::NO_MIDI_DEVICE || id == MidiDevice::INTERNAL_MIDI_DEVICE) - return id; - bool foundFriendly = false; - for(unsigned int i = 0; i < numPorts; i++) - { - try - { - auto portName = midiDevice.GetPortName(i); - bool deviceNameMatches = (portName == name); -#ifdef MODPLUG_TRACKER - if(!friendlyName.empty() && friendlyName == theApp.GetFriendlyMIDIPortName(mpt::ToUnicode(mpt::Charset::UTF8, portName), isInput, false)) - { - // Preferred match - id = i; - foundFriendly = true; - if(deviceNameMatches) - { - return id; - } - } -#else - MPT_UNREFERENCED_PARAMETER(friendlyName) -#endif - if(deviceNameMatches && !foundFriendly) - { - id = i; - } - } catch(const RtMidiError &) - { - } - } - return id; -} - - void MidiInOut::SetChunk(const ChunkData &chunk, bool /*isBank*/) { FileReader file(chunk); @@ -252,18 +201,18 @@ return; uint32 nameStrSize = file.ReadUint32LE(); - MidiDevice::ID inID = file.ReadUint32LE(); + m_inputDevice.id = file.ReadUint32LE(); uint32 inStrSize = file.ReadUint32LE(); - MidiDevice::ID outID = file.ReadUint32LE(); + m_outputDevice.id = file.ReadUint32LE(); uint32 outStrSize = file.ReadUint32LE(); uint32 flags = file.ReadUint32LE(); - std::string progName, inName, outName, inFriendlyName, outFriendlyName; + std::string progName; file.ReadString(progName, nameStrSize); m_programName = mpt::ToCString(mpt::Charset::UTF8, progName); - file.ReadString(inName, inStrSize); - file.ReadString(outName, outStrSize); + file.ReadString(m_inputDevice.name, inStrSize); + file.ReadString(m_outputDevice.name, outStrSize); if(flags & kLatencyPresent) m_latency = file.ReadDoubleLE(); @@ -271,11 +220,19 @@ m_latency = 0.0f; m_sendTimingInfo = !(flags & kIgnoreTiming); + std::string inFriendlyName, outFriendlyName; if(flags & kFriendlyInputName) file.ReadSizedString(inFriendlyName); if(flags & kFriendlyOutputName) file.ReadSizedString(outFriendlyName); + m_inputDevice.friendlyName = mpt::ToUnicode(mpt::Charset::UTF8, inFriendlyName); + m_outputDevice.friendlyName = mpt::ToUnicode(mpt::Charset::UTF8, outFriendlyName); + // Try to match port against stored name or friendly name (preferred) + m_inputDevice.id = MidiDeviceManager::FindInputPort(m_inputDevice); + m_outputDevice.id = MidiDeviceManager::FindOutputPort(m_outputDevice); + + mpt::lock_guard lock{m_mutex}; m_parameterMacros.clear(); m_initialMidiDump.clear(); m_initialDumpSent = false; @@ -304,18 +261,12 @@ } } } - - // Try to match an input port name against stored name or friendly name (preferred) - m_inputDevice.friendlyName = mpt::ToUnicode(mpt::Charset::UTF8, inFriendlyName); - m_outputDevice.friendlyName = mpt::ToUnicode(mpt::Charset::UTF8, outFriendlyName); - m_inputDevice.index = FindPort(inID, m_midiIn.getPortCount(), inName, m_inputDevice.friendlyName, m_inputDevice, true); - m_outputDevice.index = FindPort(outID, m_midiOut.getPortCount(), outName, m_outputDevice.friendlyName, m_outputDevice, false); } void MidiInOut::SetInitialMidiDump(std::vector dump) { - mpt::lock_guard lock(m_mutex); + mpt::lock_guard lock{m_mutex}; m_initialMidiDump = std::move(dump); SetModified(); } @@ -326,7 +277,7 @@ if(index < kMacroParamMin) return; index -= kMacroParamMin; - mpt::lock_guard lock(m_mutex); + mpt::lock_guard lock{m_mutex}; if(index >= m_parameterMacros.size()) m_parameterMacros.resize(index + 1); m_parameterMacroScratchSpace.reserve(macro.size() + 1); @@ -407,32 +358,39 @@ // Processing (we don't process any audio, only MIDI messages) void MidiInOut::Process(float *, float *, uint32 numFrames) { - if(m_outputDevice.index == kInternalDevice || m_midiOut.isPortOpen()) + if(IsOutputOpen()) { - mpt::lock_guard lock(m_mutex); - - if(!m_initialDumpSent && !m_initialMidiDump.empty()) + if(!m_initialDumpSent) { - try + mpt::lock_guard lock{m_mutex}; + if(!m_initialMidiDump.empty()) { MIDIMacroParser parser{mpt::as_span(m_initialMidiDump)}; mpt::span midiMsg; while(parser.NextMessage(midiMsg)) { - SendMessage(midiMsg); + MidiSend(mpt::byte_cast(midiMsg)); } - } catch(const RtMidiError &) + } + m_initialDumpSent = true; + } + + if(m_sendTimingInfo && !m_positionChanged && m_SndFile.m_PlayState.m_ppqPosFract == 0.0 && m_SndFile.m_PlayState.AtStartOfTick()) + { + // Send Song Position on every pattern change or start of new measure + uint16 ppq = mpt::saturate_trunc((m_SndFile.m_PlayState.PPQPos()) * 4.0); + if(ppq < 16384) { + MidiSend(MIDIEvents::SongPosition(ppq)); } } - m_initialDumpSent = true; // Send MIDI clock - if(m_nextClock < 1) + if(int32 nextClock = mpt::saturate_round(m_nextClock); nextClock < static_cast(numFrames)) { if(m_sendTimingInfo) { - m_outQueue.push_back(Message(GetOutputTimestamp(), MIDIEvents::sysMIDIClock)); + MidiSend(MIDIEvents::System(MIDIEvents::sysMIDIClock), nextClock); } double bpm = m_SndFile.GetCurrentBPM(); @@ -442,95 +400,45 @@ } } m_nextClock -= numFrames; + } - if(m_sendTimingInfo && !m_positionChanged && m_SndFile.m_PlayState.m_ppqPosFract == 0.0 && m_SndFile.m_PlayState.AtStartOfTick()) + if(m_outputDevice.id == kInternalDevice) + { + const int64 endOfFrame = m_SndFile.m_TimingInfo.StreamFrames + m_SndFile.m_TimingInfo.FramesRenderedInChunk + numFrames; + const auto sendMidiMsg = [endOfFrame, this](const MidiMessage &msg) { - // Send Song Position on every pattern change or start of new measure - uint16 ppq = mpt::saturate_trunc((m_SndFile.m_PlayState.PPQPos()) * 4.0); - if(ppq < 16384) - { - uint32 midiCode = MIDIEvents::SongPosition(ppq); - m_outQueue.push_back(Message(GetOutputTimestamp(), &midiCode, MIDIEvents::GetEventLength(static_cast(midiCode)))); - } - } + if(msg.m_streamFrame >= endOfFrame) + return false; + ReceiveMidi(msg); + return true; + }; + mpt::lock_guard lock{m_mutex}; + mpt::erase_if(m_internalSendQueue, sendMidiMsg); + } - double now = m_clock.Now() * (1.0 / 1000.0); - auto message = m_outQueue.begin(); - while(message != m_outQueue.end() && message->m_time <= now) - { - try - { - SendMessage(*message); - } catch(const RtMidiError &) - { - } - message++; - } - m_outQueue.erase(m_outQueue.begin(), message); - } m_positionChanged = false; } -void MidiInOut::SendMessage(mpt::span midiMsg) +// IMidiInputCallback: Called from the RtMidi callback thread +void MidiInOut::OnMidiMessage(mpt::const_byte_span data) { - if(m_outputDevice.index == kInternalDevice) - ReceiveMidi(mpt::byte_cast(midiMsg)); - else - m_midiOut.sendMessage(midiMsg.data(), midiMsg.size()); + if(!data.empty() && !IsBypassed()) + ReceiveMidi(data); } -void MidiInOut::InputCallback(double /*deltatime*/, std::vector &message) -{ - // We will check the bypass status before passing on the message, and not before entering the function, - // because otherwise we might read garbage if we toggle bypass status in the middle of a SysEx message. - bool isBypassed = IsBypassed(); - if(message.empty()) - { - return; - } else if(!m_bufferedInput.empty()) - { - // SysEx message (continued) - m_bufferedInput.insert(m_bufferedInput.end(), message.begin(), message.end()); - if(message.back() == MIDIEvents::sysExEnd) - { - // End of message found! - if(!isBypassed) - ReceiveMidi(mpt::byte_cast(mpt::as_span(m_bufferedInput))); - m_bufferedInput.clear(); - } - } else if(message.front() == MIDIEvents::sysExStart) - { - // Start of SysEx message... - if(message.back() != 0xF7) - m_bufferedInput.insert(m_bufferedInput.end(), message.begin(), message.end()); // ...but not the end! - else if(!isBypassed) - ReceiveMidi(mpt::byte_cast(mpt::as_span(message))); - } else if(!isBypassed) - { - // Regular message - ReceiveMidi(mpt::byte_cast(mpt::as_span(message))); - } -} - - // Resume playback void MidiInOut::Resume() { // Resume MIDI I/O m_isResumed = true; - m_nextClock = 0; - { - mpt::lock_guard lock(m_mutex); - m_outQueue.clear(); - } - m_clock.SetResolution(1); + m_nextClock = 0.0; OpenDevice(m_inputDevice, true); OpenDevice(m_outputDevice, false); - if(m_midiOut.isPortOpen() && m_sendTimingInfo && !m_SndFile.IsPaused()) + if(IsOutputOpen() && m_sendTimingInfo && !m_SndFile.IsPaused()) { - MidiSend(MIDIEvents::sysStart); + MidiSend(MIDIEvents::System(MIDIEvents::sysStart)); } if(m_alwaysSendInitialDump) m_initialDumpSent = false; @@ -541,31 +449,15 @@ void MidiInOut::Suspend() { // Suspend MIDI I/O - if(m_outputDevice.index == kInternalDevice || m_midiOut.isPortOpen()) + if(IsOutputOpen()) { - try + if(m_sendTimingInfo) { - mpt::lock_guard lock(m_mutex); - - // Need to flush remaining events from HardAllNotesOff - for(const auto &message : m_outQueue) - { - SendMessage(message); - } - m_outQueue.clear(); - if(m_sendTimingInfo) - { - unsigned char message[1] = {MIDIEvents::sysStop}; - SendMessage(message); - } - } catch(const RtMidiError &) - { + MidiSend(MIDIEvents::System(MIDIEvents::sysStop)); } } - //CloseDevice(inputDevice); - CloseDevice(m_outputDevice); - m_clock.SetResolution(0); + m_outputHandle.reset(); m_isResumed = false; } @@ -575,11 +467,11 @@ { if(m_sendTimingInfo && !m_SndFile.IsPaused()) { - MidiSend(MIDIEvents::sysStop); + MidiSend(MIDIEvents::System(MIDIEvents::sysStop)); uint16 ppq = mpt::saturate_trunc((m_SndFile.m_PlayState.PPQPos()) * 4.0); if(ppq < 16384) MidiSend(MIDIEvents::SongPosition(ppq)); - MidiSend(MIDIEvents::sysStart); + MidiSend(MIDIEvents::System(MIDIEvents::sysStart)); } m_positionChanged = true; } @@ -587,25 +479,29 @@ void MidiInOut::Bypass(bool bypass) { - if(bypass) - { - mpt::lock_guard lock(m_mutex); - m_outQueue.clear(); - } IMidiPlugin::Bypass(bypass); } -bool MidiInOut::MidiSend(mpt::const_byte_span midiData) +bool MidiInOut::MidiSend(mpt::const_byte_span midiData, int32 frameOffset) { - if((m_outputDevice.index != kInternalDevice && !m_midiOut.isPortOpen()) || IsBypassed()) + const int64 outputStreamFrame = GetOutputStreamFrame(frameOffset); + if(m_outputDevice.id == kInternalDevice) { - // We need an output device to send MIDI messages to. + if(outputStreamFrame < m_SndFile.m_TimingInfo.StreamFrames + m_SndFile.m_TimingInfo.FramesRenderedInChunk) + { + ReceiveMidi(midiData); + } else + { + mpt::lock_guard lock{m_mutex}; + m_internalSendQueue.emplace_back(outputStreamFrame, midiData); + } return true; } + if(!m_outputHandle || IsBypassed() || m_SndFile.IsRenderingToDisc()) + return true; - mpt::lock_guard lock(m_mutex); - m_outQueue.push_back(Message(GetOutputTimestamp(), midiData.data(), midiData.size())); + m_outputHandle->Send(midiData, outputStreamFrame); return true; } @@ -647,103 +543,78 @@ // Open a device for input or output. -void MidiInOut::OpenDevice(MidiDevice newDevice, bool asInputDevice) +void MidiInOut::OpenDevice(const MidiPortInfo &newDevice, bool asInputDevice) { - newDevice.index = FindPort(newDevice.index, asInputDevice ? m_midiIn.getPortCount() : m_midiOut.getPortCount(), newDevice.name, newDevice.friendlyName, newDevice, asInputDevice); - OpenDevice(newDevice.index, asInputDevice, false); + OpenDevice(MidiDeviceManager::FindPort(newDevice, asInputDevice), asInputDevice, false); } // Open a device for input or output. -void MidiInOut::OpenDevice(MidiDevice::ID newDevice, bool asInputDevice, bool updateName) +void MidiInOut::OpenDevice(MidiPortID newDevice, bool asInputDevice, bool updateName) { - MidiDevice &device = asInputDevice ? m_inputDevice : m_outputDevice; + MidiPortInfo &device = asInputDevice ? m_inputDevice : m_outputDevice; + const bool wasOpen = asInputDevice ? m_inputHandle.get() != nullptr : m_outputHandle.get() != nullptr; - if(device.index == newDevice && device.stream.isPortOpen()) + if(wasOpen && device.id == newDevice) { // No need to re-open this device. return; } - CloseDevice(device); + if(asInputDevice) + m_inputHandle.reset(); + else + m_outputHandle.reset(); + device.id = newDevice; - device.index = newDevice; - device.stream.closePort(); - - if(device.index == kNoDevice) + if(newDevice == kNoDevice || newDevice == kInternalDevice) { - // Dummy device + // Dummy or internal device if(updateName) { - device.name = ""; + device.name = (newDevice == kInternalDevice) ? "" : ""; device.friendlyName.clear(); } return; - } else if(device.index == kInternalDevice) - { - if(updateName) - { - device.name = ""; - device.friendlyName.clear(); - } - return; } - if(updateName) + bool ok = false; + MidiDeviceManager &manager = MidiDeviceManager::Instance(); + if(asInputDevice) { - device.name = device.GetPortName(newDevice); -#ifdef MODPLUG_TRACKER - device.friendlyName = theApp.GetFriendlyMIDIPortName(mpt::ToUnicode(mpt::Charset::UTF8, device.name), asInputDevice, false); -#endif // MODPLUG_TRACKER + m_inputHandle = manager.OpenInput(newDevice, this); + ok = m_inputHandle != nullptr; + if(ok && updateName) + m_inputDevice = manager.GetPortInfo(*m_inputHandle); + } else + { + m_outputHandle = manager.OpenOutput(newDevice); + ok = m_outputHandle != nullptr; + if (ok && updateName) + m_outputDevice = manager.GetPortInfo(*m_outputHandle); } - //if(m_isResumed) + + if(!ok) { - mpt::lock_guard lock(m_mutex); - - try + if(updateName) + device.name = "Unavailable"; + MidiInOutEditor *editor = dynamic_cast(GetEditor()); + if(editor != nullptr) { - device.stream.openPort(newDevice); - if(asInputDevice) - { - m_midiIn.setCallback(InputCallback, this); - m_midiIn.ignoreTypes(false, true, true); - } - } catch(RtMidiError &error) - { - device.name = "Unavailable"; - MidiInOutEditor *editor = dynamic_cast(GetEditor()); - if(editor != nullptr) - { - Reporting::Error("MIDI device cannot be opened. Is it open in another application?\n\n" + error.getMessage(), "MIDI Input / Output", editor); - } + Reporting::Error("MIDI device cannot be opened. Is it open in another application?", "MIDI Input / Output", editor); } } } -// Close an active device. -void MidiInOut::CloseDevice(MidiDevice &device) +// Calculate the output stream frame position for the current render position +int64 MidiInOut::GetOutputStreamFrame(int32 frameOffset) const { - if(device.stream.isPortOpen()) - { - mpt::lock_guard lock(m_mutex); - device.stream.closePort(); - } + return m_SndFile.m_TimingInfo.StreamFrames + + m_SndFile.m_TimingInfo.FramesRenderedInChunk + + frameOffset + + mpt::saturate_round(m_latency * m_SndFile.GetSampleRate()); } -// Calculate the current output timestamp -double MidiInOut::GetOutputTimestamp() const -{ - return m_clock.Now() * (1.0 / 1000.0) + (m_outputDevice.index == kInternalDevice ? 0.0 : GetOutputLatency()) + m_latency; -} - - -// Get a device name -std::string MidiDevice::GetPortName(MidiDevice::ID port) -{ - return stream.getPortName(port); -} - - OPENMPT_NAMESPACE_END Index: mptrack/plugins/MidiInOut.h =================================================================== --- mptrack/plugins/MidiInOut.h (revision 25320) +++ mptrack/plugins/MidiInOut.h (working copy) @@ -13,40 +13,16 @@ #include "openmpt/all/BuildSettings.hpp" #include "mpt/mutex/mutex.hpp" -#include "../../misc/mptClock.h" +#include "../MidiDeviceManager.h" +#include "../MidiInputCallback.h" #include "../../soundlib/plugins/PlugInterface.h" -#include -#include -#include OPENMPT_NAMESPACE_BEGIN -class MidiDevice +class MidiInOut final : public IMidiPlugin, public IMidiInputCallback { -public: - using ID = decltype(RtMidiIn().getPortCount()); - static constexpr ID NO_MIDI_DEVICE = ID(-1); - static constexpr ID INTERNAL_MIDI_DEVICE = ID(-2); - - RtMidi &stream; - std::string name; // Charset::UTF8 - mpt::ustring friendlyName; - ID index = NO_MIDI_DEVICE; - -public: - MidiDevice(RtMidi &stream) - : stream(stream) - , name("") - { } - - std::string GetPortName(ID port); // Charset::UTF8 -}; - - -class MidiInOut final : public IMidiPlugin -{ friend class MidiInOutEditor; protected: @@ -59,88 +35,27 @@ kNumParams = kMacroParamMax + 1, kNumVisibleParams = 0, - kNoDevice = MidiDevice::NO_MIDI_DEVICE, - kInternalDevice = MidiDevice::INTERNAL_MIDI_DEVICE, + kNoDevice = kNoMidiDevice, + kInternalDevice = MidiPortID(-2), }; - // MIDI queue entry with small storage optimiziation. - // This optimiziation is going to be used for all messages that OpenMPT can send internally, - // but SysEx messages created by this plugin or received from other plugins may be longer. - class Message - { - public: - double m_time; - size_t m_size; - unsigned char *m_message = nullptr; - protected: - std::array m_msgSmall; + std::string m_chunkData; // Storage for GetChunk - public: - Message(double time, const void *data, size_t size) - : m_time(time) - , m_size(size) - { - if(size > m_msgSmall.size()) - m_message = new unsigned char[size]; - else - m_message = m_msgSmall.data(); - std::memcpy(m_message, data, size); - } - - Message(const Message &) = delete; - Message & operator=(const Message &) = delete; - - Message(double time, unsigned char msg) noexcept : Message(time, &msg, 1) { } - - operator mpt::span() const { return mpt::as_span(m_message, m_size); } - - Message(Message &&other) noexcept - : m_time(other.m_time) - , m_size(other.m_size) - , m_message(other.m_message) - , m_msgSmall(other.m_msgSmall) - { - other.m_message = nullptr; - if(m_size <= m_msgSmall.size()) - m_message = m_msgSmall.data(); - - } - - ~Message() - { - if(m_size > m_msgSmall.size()) - delete[] m_message; - } - - Message& operator= (Message &&other) noexcept - { - m_time = other.m_time; - m_size = other.m_size; - m_message = (m_size <= m_msgSmall.size()) ? m_msgSmall.data() : other.m_message; - m_msgSmall = other.m_msgSmall; - other.m_message = nullptr; - return *this; - } - }; - - std::string m_chunkData; // Storage for GetChunk - std::deque m_outQueue; // Latency-compensated output - std::vector m_bufferedInput; // For receiving long SysEx messages - + mpt::mutex m_mutex; // For initial MIDI dump, macros and internal send queue + std::vector m_internalSendQueue; std::vector m_initialMidiDump; // MIDI dump to send at song start std::vector> m_parameterMacros; // Macros to automate via plugin parameter mechanism std::vector m_parameterMacroScratchSpace; - mpt::mutex m_mutex; double m_nextClock = 0.0; // Remaining samples until next MIDI clock tick should be sent double m_latency = 0.0; // User-adjusted latency in seconds // I/O device settings - Util::MultimediaClock m_clock; - RtMidiIn m_midiIn; - RtMidiOut m_midiOut; - MidiDevice m_inputDevice; - MidiDevice m_outputDevice; + std::unique_ptr m_inputHandle; + std::unique_ptr m_outputHandle; + MidiPortInfo m_inputDevice; + MidiPortInfo m_outputDevice; + bool m_sendTimingInfo = true; bool m_positionChanged = false; bool m_alwaysSendInitialDump = false; @@ -157,64 +72,63 @@ ///////////////////////////////////////////////// // Destroy the plugin - int32 GetUID() const final { return 'MMID'; } - int32 GetVersion() const final { return 2; } - void Idle() final { } - uint32 GetLatency() const final; + int32 GetUID() const override { return 'MMID'; } + int32 GetVersion() const override { return 2; } + void Idle() override { } + uint32 GetLatency() const override; - int32 GetNumPrograms() const final { return kNumPrograms; } - int32 GetCurrentProgram() final { return 0; } - void SetCurrentProgram(int32) final { } + int32 GetNumPrograms() const override { return kNumPrograms; } + int32 GetCurrentProgram() override { return 0; } + void SetCurrentProgram(int32) override { } - PlugParamIndex GetNumParameters() const final { return kNumParams; } - PlugParamIndex GetNumVisibleParameters() const final { return kNumVisibleParams; } - void SetParameter(PlugParamIndex index, PlugParamValue value, PlayState *playState = nullptr, CHANNELINDEX chn = CHANNELINDEX_INVALID) final; - PlugParamValue GetParameter(PlugParamIndex nIndex) final; + PlugParamIndex GetNumParameters() const override { return kNumParams; } + PlugParamIndex GetNumVisibleParameters() const override { return kNumVisibleParams; } + void SetParameter(PlugParamIndex index, PlugParamValue value, PlayState *playState = nullptr, CHANNELINDEX chn = CHANNELINDEX_INVALID) override; + PlugParamValue GetParameter(PlugParamIndex nIndex) override; // Save parameters for storing them in a module file - void SaveAllParameters() final; + void SaveAllParameters() override; // Restore parameters from module file - void RestoreAllParameters(int32 program) final; - void Process(float *pOutL, float *pOutR, uint32 numFrames) final; + void RestoreAllParameters(int32 program) override; + void Process(float *pOutL, float *pOutR, uint32 numFrames) override; // Render silence and return the highest resulting output level - float RenderSilence(uint32) final{ return 0; } - using IMixPlugin::MidiSend; - bool MidiSend(mpt::const_byte_span midiData) final; - void HardAllNotesOff() final; - // Modify parameter by given amount. Only needs to be re-implemented if plugin architecture allows this to be performed atomically. - void Resume() final; - void Suspend() final; + float RenderSilence(uint32) override{ return 0; } + bool MidiSend(mpt::const_byte_span midiData) override { return MidiSend(midiData, 0); } + bool MidiSend(mpt::const_byte_span midiData, int32 frameOffset); + void HardAllNotesOff() override; + void Resume() override; + void Suspend() override; // Tell the plugin that there is a discontinuity between the previous and next render call (e.g. aftert jumping around in the module) - void PositionChanged() final; - void Bypass(bool bypass = true) final; - bool IsInstrument() const final { return true; } - bool CanRecieveMidiEvents() final { return true; } + void PositionChanged() override; + void Bypass(bool bypass = true) override; + bool IsInstrument() const override { return true; } + bool CanRecieveMidiEvents() override { return true; } // If false is returned, mixing this plugin can be skipped if its input are currently completely silent. - bool ShouldProcessSilence() final { return true; } + bool ShouldProcessSilence() override { return true; } #ifdef MODPLUG_TRACKER - CString GetDefaultEffectName() final { return _T("MIDI Input / Output"); } + CString GetDefaultEffectName() override { return _T("MIDI Input / Output"); } - CString GetParamName(PlugParamIndex param) final; - CString GetParamLabel(PlugParamIndex) final{ return CString(); } - CString GetParamDisplay(PlugParamIndex param) final; - CString GetCurrentProgramName() final { return m_programName; } - void SetCurrentProgramName(const CString &name) final { m_programName = name; } - CString GetProgramName(int32) final { return m_programName; } + CString GetParamName(PlugParamIndex param) override; + CString GetParamLabel(PlugParamIndex) override{ return CString(); } + CString GetParamDisplay(PlugParamIndex param) override; + CString GetCurrentProgramName() override { return m_programName; } + void SetCurrentProgramName(const CString &name) override { m_programName = name; } + CString GetProgramName(int32) override { return m_programName; } virtual CString GetPluginVendor() { return _T("OpenMPT Project"); } - bool HasEditor() const final { return true; } + bool HasEditor() const override { return true; } protected: - CAbstractVstEditor *OpenEditor() final; + CAbstractVstEditor *OpenEditor() override; #endif public: - int GetNumInputChannels() const final { return 0; } - int GetNumOutputChannels() const final { return 0; } + int GetNumInputChannels() const override { return 0; } + int GetNumOutputChannels() const override { return 0; } - bool ProgramsAreChunks() const final { return true; } - ChunkData GetChunk(bool isBank) final; - void SetChunk(const ChunkData &chunk, bool isBank) final; + bool ProgramsAreChunks() const override { return true; } + ChunkData GetChunk(bool isBank) override; + void SetChunk(const ChunkData &chunk, bool isBank) override; void SetInitialMidiDump(std::vector dump); std::vector GetInitialMidiDump() const { return m_initialMidiDump; } @@ -221,20 +135,17 @@ void SetMacro(size_t index, std::string macro); std::string GetMacro(size_t index) const; + void OnMidiMessage(mpt::const_byte_span data) override; + protected: // Open a device for input or output. - void OpenDevice(MidiDevice newDevice, bool asInputDevice); - void OpenDevice(MidiDevice::ID newDevice, bool asInputDevice, bool updateName = true); - // Close an active device. - void CloseDevice(MidiDevice &device); + void OpenDevice(const MidiPortInfo &newDevice, bool asInputDevice); + void OpenDevice(MidiPortID newDevice, bool asInputDevice, bool updateName = true); - static void InputCallback(double deltatime, std::vector *message, void *userData) { static_cast(userData)->InputCallback(deltatime, *message); } - void InputCallback(double deltatime, std::vector &message); + // Calculate the audio device stream frame position for the current output + int64 GetOutputStreamFrame(int32 frameOffset) const; - // Calculate the current output timestamp - double GetOutputTimestamp() const; - - void SendMessage(mpt::span midiMsg); + bool IsOutputOpen() const { return m_outputDevice.id == kInternalDevice || m_outputHandle; } }; Index: mptrack/plugins/MidiInOutEditor.cpp =================================================================== --- mptrack/plugins/MidiInOutEditor.cpp (revision 25320) +++ mptrack/plugins/MidiInOutEditor.cpp (working copy) @@ -19,7 +19,6 @@ #include "../UpdateHints.h" #include "../../soundlib/MIDIEvents.h" #include "../../soundlib/MIDIMacroParser.h" -#include OPENMPT_NAMESPACE_BEGIN @@ -71,8 +70,8 @@ m_latencyEdit.AllowNegative(true); m_latencyEdit.SetDecimalValue(plugin.m_latency * 1000.0, 4); m_latencySpin.SetRange32(mpt::saturate_round(plugin.GetOutputLatency() * -1000.0), int32_max); - PopulateList(m_inputCombo, plugin.m_midiIn, plugin.m_inputDevice, true); - PopulateList(m_outputCombo, plugin.m_midiOut, plugin.m_outputDevice, false); + PopulateList(m_inputCombo, true, plugin.m_inputDevice.id); + PopulateList(m_outputCombo, false, plugin.m_outputDevice.id); UpdateOutputPlugin(); CheckDlgButton(IDC_CHECK1, plugin.m_sendTimingInfo ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(IDC_CHECK2, plugin.m_alwaysSendInitialDump ? BST_UNCHECKED : BST_CHECKED); @@ -128,35 +127,30 @@ // Update lists of available input / output devices -void MidiInOutEditor::PopulateList(CComboBox &combo, RtMidi &rtDevice, MidiDevice &midiDevice, bool isInput) +void MidiInOutEditor::PopulateList(CComboBox &combo, bool isInput, MidiPortID currentPort) { combo.SetRedraw(FALSE); combo.ResetContent(); // Add dummy device - combo.SetItemData(combo.AddString(_T("")), static_cast(MidiInOut::kNoDevice)); + combo.SetItemData(combo.AddString(_T("")), static_cast(kNoMidiDevice)); if(!isInput) { combo.SetItemData(combo.AddString(_T("Internal OpenMPT Output")), static_cast(MidiInOut::kInternalDevice)); } - // Go through all RtMidi devices - auto ports = rtDevice.getPortCount(); + // Go through all MIDI devices + MidiDeviceManager &manager = MidiDeviceManager::Instance(); + const auto ports = isInput ? manager.EnumerateInputPorts() : manager.EnumerateOutputPorts(); int selectedItem = 0; - CString portName; - for(unsigned int i = 0; i < ports; i++) + for(const auto &port : ports) { - try - { - portName = theApp.GetFriendlyMIDIPortName(mpt::ToCString(mpt::Charset::UTF8, midiDevice.GetPortName(i)), isInput); - int result = combo.AddString(portName); - combo.SetItemData(result, i); + CString portName = theApp.GetFriendlyMIDIPortName(mpt::ToCString(mpt::Charset::UTF8, port.name), isInput); + int result = combo.AddString(portName); + combo.SetItemData(result, port.id); - if(result != CB_ERR && i == midiDevice.index) - selectedItem = result; - } catch(RtMidiError &) - { - } + if(result != CB_ERR && port.id == currentPort) + selectedItem = result; } combo.SetCurSel(selectedItem); @@ -180,12 +174,12 @@ // Refresh current input / output device in GUI -void MidiInOutEditor::SetCurrentDevice(CComboBox &combo, MidiDevice::ID device) +void MidiInOutEditor::SetCurrentDevice(CComboBox &combo, MidiPortID device) { int items = combo.GetCount(); for(int i = 0; i < items; i++) { - if(static_cast(combo.GetItemData(i)) == device) + if(static_cast(combo.GetItemData(i)) == device) { combo.SetCurSel(i); break; @@ -196,7 +190,7 @@ void MidiInOutEditor::OnInputChanged() { - MidiDevice::ID newDevice = static_cast(m_inputCombo.GetItemData(m_inputCombo.GetCurSel())); + MidiPortID newDevice = static_cast(m_inputCombo.GetItemData(m_inputCombo.GetCurSel())); static_cast(m_VstPlugin).OpenDevice(newDevice, true); } @@ -203,7 +197,7 @@ void MidiInOutEditor::OnOutputChanged() { - MidiDevice::ID newDevice = static_cast(m_outputCombo.GetItemData(m_outputCombo.GetCurSel())); + MidiPortID newDevice = static_cast(m_outputCombo.GetItemData(m_outputCombo.GetCurSel())); static_cast(m_VstPlugin).OpenDevice(newDevice, false); } Index: mptrack/plugins/MidiInOutEditor.h =================================================================== --- mptrack/plugins/MidiInOutEditor.h (revision 25320) +++ mptrack/plugins/MidiInOutEditor.h (working copy) @@ -35,7 +35,7 @@ MidiInOutEditor(MidiInOut &plugin); // Refresh current input / output device in GUI - void SetCurrentDevice(bool asInputDevice, MidiDevice::ID device) + void SetCurrentDevice(bool asInputDevice, MidiPortID device) { CComboBox &combo = asInputDevice ? m_inputCombo : m_outputCombo; SetCurrentDevice(combo, device); @@ -49,10 +49,10 @@ protected: // Update lists of available input / output devices - static void PopulateList(CComboBox &combo, RtMidi &rtDevice, MidiDevice &midiDevice, bool isInput); + static void PopulateList(CComboBox &combo, bool isInput, MidiPortID currentPort); void UpdateOutputPlugin(); // Refresh current input / output device in GUI - void SetCurrentDevice(CComboBox &combo, MidiDevice::ID device); + void SetCurrentDevice(CComboBox &combo, MidiPortID device); void UpdateMidiDump(); Index: mptrack/plugins/VstDefinitions.h =================================================================== --- mptrack/plugins/VstDefinitions.h (revision 25320) +++ mptrack/plugins/VstDefinitions.h (working copy) @@ -659,7 +659,7 @@ { int32 noteLength; int32 noteOffset; - uint32 midiData; + std::array midiData; int8 detune; int8 noteOffVelocity; int8 reserved1; Index: mptrack/TrackerSettings.cpp =================================================================== --- mptrack/TrackerSettings.cpp (revision 25320) +++ mptrack/TrackerSettings.cpp (working copy) @@ -14,6 +14,7 @@ #include "ExceptionHandler.h" #include "HighDPISupport.h" #include "Mainfrm.h" +#include "MidiDeviceManager.h" #include "Moddoc.h" #include "Mpdlgs.h" #include "Mptrack.h" @@ -1527,11 +1528,14 @@ void TrackerSettings::SetMIDIDevice(UINT id) { m_nMidiDevice = id; - MIDIINCAPS mic; - mic.szPname[0] = 0; - if(midiInGetDevCaps(id, &mic, sizeof(mic)) == MMSYSERR_NOERROR) + const auto ports = MidiDeviceManager::Instance().EnumerateInputPorts(); + for(const auto &port : ports) { - midiDeviceName = mic.szPname; + if(port.id == id) + { + midiDeviceName = mpt::ToCString(mpt::Charset::UTF8, port.name); + return; + } } } @@ -1544,31 +1548,22 @@ CString deviceName = midiDeviceName; deviceName.TrimRight(); - MIDIINCAPS mic; - UINT candidate = m_nMidiDevice, numDevs = midiInGetNumDevs(); - for(UINT i = 0; i < numDevs; i++) + const auto ports = MidiDeviceManager::Instance().EnumerateInputPorts(); + size_t numDevs = ports.size(); + MidiPortID candidate = m_nMidiDevice; + for(size_t i = 0; i < numDevs; i++) { - mic.szPname[0] = 0; - if(midiInGetDevCaps(i, &mic, sizeof(mic)) != MMSYSERR_NOERROR) - continue; - // Some device names have trailing spaces (e.g. "USB MIDI Interface "), but those may get lost in our settings framework. - mpt::String::SetNullTerminator(mic.szPname); - size_t strLen = _tcslen(mic.szPname); - while(strLen-- > 0) + CString portName = mpt::ToCString(mpt::Charset::UTF8, ports[i].name); + portName.TrimRight(); + if(portName == deviceName) { - if(mic.szPname[strLen] == _T(' ')) - mic.szPname[strLen] = 0; - else - break; - } - if(CString(mic.szPname) == deviceName) - { - candidate = i; - numDevs = m_nMidiDevice + 1; + candidate = ports[i].id; // If the same device name exists twice, try to match both device number and name if(candidate == m_nMidiDevice) return candidate; + // Already found a matching name, only need to check up to the device number we want to match + numDevs = m_nMidiDevice + 1; } } // If the device changed its ID, update it now. Index: mptrack/view_com.cpp =================================================================== --- mptrack/view_com.cpp (revision 25320) +++ mptrack/view_com.cpp (working copy) @@ -88,7 +88,6 @@ ON_WM_SIZE() ON_WM_DESTROY() ON_MESSAGE(WM_MOD_KEYCOMMAND, &CViewComments::OnCustomKeyMsg) - ON_MESSAGE(WM_MOD_MIDIMSG, &CViewComments::OnMidiMsg) ON_COMMAND(IDC_LIST_SAMPLES, &CViewComments::OnShowSamples) ON_COMMAND(IDC_LIST_INSTRUMENTS, &CViewComments::OnShowInstruments) ON_COMMAND(IDC_LIST_PATTERNS, &CViewComments::OnShowPatterns) @@ -190,9 +189,14 @@ } -LRESULT CViewComments::OnMidiMsg(WPARAM midiData_, LPARAM) +bool CViewComments::PreFilterMidiMessage(mpt::const_byte_span midiData) { - uint32 midiData = static_cast(midiData_); + return DefaultPreFilterMidiMessage(midiData, &GetDocument()->GetSoundFile(), kCtxViewComments); +} + + +void CViewComments::OnMidiMessage(mpt::const_byte_span midiData) +{ INSTRUMENTINDEX ins = 0; SAMPLEINDEX smp = 0; @@ -202,8 +206,7 @@ else if(item > 0 && m_nListId == IDC_LIST_INSTRUMENTS) ins = static_cast(item); - GetDocument()->ProcessMIDI(midiData, smp, ins, GetDocument()->GetSoundFile().GetInstrumentPlugin(ins), kCtxViewComments); - return 1; + GetDocument()->ProcessMIDI(midiData, smp, ins, GetDocument()->GetSoundFile().GetInstrumentPlugin(ins)); } Index: mptrack/view_com.h =================================================================== --- mptrack/view_com.h (revision 25320) +++ mptrack/view_com.h (working copy) @@ -14,12 +14,13 @@ #include "openmpt/all/BuildSettings.hpp" #include "CListCtrl.h" +#include "MidiInputCallback.h" #include "Globals.h" #include "../soundlib/modcommand.h" OPENMPT_NAMESPACE_BEGIN -class CViewComments: public CModScrollView +class CViewComments: public CModScrollView, public IMidiInputCallback { public: CViewComments() = default; @@ -47,6 +48,8 @@ BOOL PreTranslateMessage(MSG *pMsg) override; LRESULT OnModViewMsg(WPARAM wParam, LPARAM lParam) override; void UpdateView(UpdateHint hint, CObject *pObject = nullptr) override; + bool PreFilterMidiMessage(mpt::const_byte_span midiData) override; + void OnMidiMessage(mpt::const_byte_span midiData) override; //}}AFX_VIRTUAL protected: @@ -66,7 +69,6 @@ afx_msg void OnCustomDrawList(NMHDR *pNMHDR, LRESULT *pResult); afx_msg void OnCopyNames(); afx_msg void OnPasteNames(); - afx_msg LRESULT OnMidiMsg(WPARAM midiData, LPARAM); afx_msg LRESULT OnCustomKeyMsg(WPARAM, LPARAM); //}}AFX_MSG DECLARE_MESSAGE_MAP() Index: mptrack/View_gen.cpp =================================================================== --- mptrack/View_gen.cpp (revision 25320) +++ mptrack/View_gen.cpp (working copy) @@ -117,7 +117,6 @@ ON_NOTIFY(TCN_SELCHANGE, IDC_TABCTRL1, &CViewGlobals::OnTabSelchange) ON_MESSAGE(WM_MOD_UNLOCKCONTROLS, &CViewGlobals::OnUnlockControls) ON_MESSAGE(WM_MOD_VIEWMSG, &CViewGlobals::OnModViewMsg) - ON_MESSAGE(WM_MOD_MIDIMSG, &CViewGlobals::OnMidiMsg) ON_MESSAGE(WM_MOD_PLUGPARAMAUTOMATE, &CViewGlobals::OnParamAutomated) ON_MESSAGE(WM_MOD_PLUGINDRYWETRATIOCHANGED, &CViewGlobals::OnDryWetRatioChangedFromPlayer) @@ -274,20 +273,15 @@ } -LRESULT CViewGlobals::OnMidiMsg(WPARAM midiData_, LPARAM) +bool CViewGlobals::PreFilterMidiMessage(mpt::const_byte_span midiData) { - uint32 midiData = static_cast(midiData_); - // Handle MIDI messages assigned to shortcuts - CInputHandler *ih = CMainFrame::GetInputHandler(); - if(ih->HandleMIDIMessage(kCtxViewGeneral, midiData) == kcNull) - ih->HandleMIDIMessage(kCtxAllContexts, midiData); - return 1; + return DefaultPreFilterMidiMessage(midiData, &GetDocument()->GetSoundFile(), kCtxViewGeneral); } void CViewGlobals::RecalcLayout() { - if (m_TabCtrl.m_hWnd != NULL) + if(m_TabCtrl.m_hWnd) { CRect rect; GetClientRect(&rect); Index: mptrack/View_gen.h =================================================================== --- mptrack/View_gen.h (revision 25320) +++ mptrack/View_gen.h (working copy) @@ -14,6 +14,7 @@ #include "openmpt/all/BuildSettings.hpp" #include "AccessibleControls.h" #include "ColorPickerButton.h" +#include "MidiInputCallback.h" #include "PluginComboBox.h" #include "UpdateHints.h" @@ -26,7 +27,7 @@ class CModDoc; class IMixPlugin; -class CViewGlobals: public CFormView +class CViewGlobals: public CFormView, public IMidiInputCallback { protected: CRect m_rcClient; @@ -75,10 +76,11 @@ void DoDataExchange(CDataExchange *pDX) override; void OnUpdate(CView *pSender, LPARAM lHint, CObject *pHint) override; INT_PTR OnToolHitTest(CPoint point, TOOLINFO *pTI) const override; + bool PreFilterMidiMessage(mpt::const_byte_span midiData) override; + void OnMidiMessage(mpt::const_byte_span /*midiData*/) override { } void UpdateView(UpdateHint hint, CObject *pObj = nullptr); LRESULT OnModViewMsg(WPARAM, LPARAM); - LRESULT OnMidiMsg(WPARAM midiData, LPARAM); private: void PrepareUndo(CHANNELINDEX chnMod4); Index: mptrack/View_ins.cpp =================================================================== --- mptrack/View_ins.cpp (revision 25320) +++ mptrack/View_ins.cpp (working copy) @@ -126,7 +126,6 @@ ON_COMMAND(ID_ENVELOPE_TOGGLERELEASENODE, &CViewInstrument::OnEnvToggleReleasNode) ON_COMMAND(ID_ENVELOPE_SCALEPOINTS, &CViewInstrument::OnEnvelopeScalePoints) - ON_MESSAGE(WM_MOD_MIDIMSG, &CViewInstrument::OnMidiMsg) ON_MESSAGE(WM_MOD_KEYCOMMAND, &CViewInstrument::OnCustomKeyMsg) ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, &CViewInstrument::OnUpdateUndo) @@ -2092,24 +2091,27 @@ } -LRESULT CViewInstrument::OnMidiMsg(WPARAM midiDataParam, LPARAM) +bool CViewInstrument::PreFilterMidiMessage(mpt::const_byte_span midiData) { - const uint32 midiData = static_cast(midiDataParam); + return DefaultPreFilterMidiMessage(midiData, &GetDocument()->GetSoundFile(), kCtxViewInstruments); +} + + +void CViewInstrument::OnMidiMessage(mpt::const_byte_span midiData) +{ CModDoc *modDoc = GetDocument(); - if(modDoc != nullptr) + if(modDoc == nullptr || midiData.empty()) + return; + + modDoc->ProcessMIDI(midiData, 0, m_nInstrument, modDoc->GetSoundFile().GetInstrumentPlugin(m_nInstrument)); + + MIDIEvents::EventType event = MIDIEvents::GetTypeFromEvent(midiData); + if(event == MIDIEvents::evNoteOn) { - modDoc->ProcessMIDI(midiData, 0, m_nInstrument, modDoc->GetSoundFile().GetInstrumentPlugin(m_nInstrument), kCtxViewInstruments); - - MIDIEvents::EventType event = MIDIEvents::GetTypeFromEvent(midiData); uint8 midiByte1 = MIDIEvents::GetDataByte1FromEvent(midiData); - if(event == MIDIEvents::evNoteOn) - { + if(MIDIEvents::GetDataByte2FromEvent(midiData) > 0) CMainFrame::GetMainFrame()->SetInfoText(mpt::ToCString(modDoc->GetSoundFile().GetNoteName(midiByte1 + NOTE_MIN, m_nInstrument))); - } - - return 1; } - return 0; } Index: mptrack/View_ins.h =================================================================== --- mptrack/View_ins.h (revision 25320) +++ mptrack/View_ins.h (working copy) @@ -13,6 +13,7 @@ #include "openmpt/all/BuildSettings.hpp" #include "Globals.h" +#include "MidiInputCallback.h" #include "Moddoc.h" #include "../soundlib/Snd_defs.h" @@ -35,7 +36,7 @@ ENV_DRAGNEXT = (MAX_ENVPOINTS + 6), }; -class CViewInstrument: public CModScrollView +class CViewInstrument: public CModScrollView, public IMidiInputCallback { protected: CPoint m_ptMenu; @@ -191,6 +192,8 @@ LRESULT OnPlayerNotify(Notification *) override; HRESULT get_accName(VARIANT varChild, BSTR *pszName) override; INT_PTR OnToolHitTest(CPoint point, TOOLINFO *pTI) const override; + bool PreFilterMidiMessage(mpt::const_byte_span midiData) override; + void OnMidiMessage(mpt::const_byte_span midiData) override; //}}AFX_VIRTUAL protected: @@ -237,7 +240,6 @@ afx_msg void OnEnvelopeScalePoints(); afx_msg void OnDropFiles(HDROP hDropInfo); afx_msg LRESULT OnCustomKeyMsg(WPARAM, LPARAM); - afx_msg LRESULT OnMidiMsg(WPARAM, LPARAM); // cppcheck-suppress duplInheritedMember afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt); afx_msg void OnXButtonUp(UINT nFlags, UINT nButton, CPoint point); Index: mptrack/View_pat.cpp =================================================================== --- mptrack/View_pat.cpp (revision 25320) +++ mptrack/View_pat.cpp (working copy) @@ -62,7 +62,6 @@ ON_WM_KILLFOCUS() ON_WM_DESTROY() ON_MESSAGE(WM_MOD_KEYCOMMAND, &CViewPattern::OnCustomKeyMsg) - ON_MESSAGE(WM_MOD_MIDIMSG, &CViewPattern::OnMidiMsg) ON_MESSAGE(WM_MOD_RECORDPARAM, &CViewPattern::OnRecordPlugParamChange) ON_COMMAND(ID_EDIT_CUT, &CViewPattern::OnEditCut) ON_COMMAND(ID_EDIT_COPY, &CViewPattern::OnEditCopy) @@ -199,6 +198,8 @@ CModDoc *modDoc = GetDocument(); if(modDoc->GetSoundFile().m_SongFlags[SONG_FORMAT_NO_VOLCOL] && TrackerSettings::Instance().autoHideVolumeColumnForMOD) m_visibleColumns.reset(PatternCursor::volumeColumn); + + CMainFrame::GetMainFrame()->SetMidiRecordCallback(this, MidiTransformers::Sustain | MidiTransformers::Transpose); } @@ -3935,54 +3936,19 @@ } -LRESULT CViewPattern::OnMidiMsg(WPARAM dwMidiDataParam, LPARAM) +bool CViewPattern::PreFilterMidiMessage(mpt::const_byte_span midiData) { - const uint32 midiData = static_cast(dwMidiDataParam); - static uint8 midiVolume = 127; - - CMainFrame *pMainFrm = CMainFrame::GetMainFrame(); CModDoc *pModDoc = GetDocument(); + if(pModDoc == nullptr || midiData.empty()) + return false; - if(pModDoc == nullptr || pMainFrm == nullptr) - return 0; - CSoundFile &sndFile = pModDoc->GetSoundFile(); - - //Midi message from our perspective: - // +---------------------------+---------------------------+-------------+-------------+ - //bit: | 24.23.22.21 | 20.19.18.17 | 16.15.14.13 | 12.11.10.09 | 08.07.06.05 | 04.03.02.01 | - // +---------------------------+---------------------------+-------------+-------------+ - // | Velocity (0-127) | Note (middle C is 60) | Event | Channel | - // +---------------------------+---------------------------+-------------+-------------+ - //(http://home.roadrunner.com/~jgglatt/tech/midispec.htm) - - const uint8 midiByte1 = MIDIEvents::GetDataByte1FromEvent(midiData); - const uint8 midiByte2 = MIDIEvents::GetDataByte2FromEvent(midiData); - const uint8 channel = MIDIEvents::GetChannelFromEvent(midiData); - - const uint8 nNote = midiByte1 + NOTE_MIN; - int vol = midiByte2; // At this stage nVol is a non linear value in [0;127] - // Need to convert to linear in [0;64] - see below - MIDIEvents::EventType event = MIDIEvents::GetTypeFromEvent(midiData); - - if((event == MIDIEvents::evNoteOn) && !vol) - event = MIDIEvents::evNoteOff; //Convert event to note-off if req'd - - // Handle MIDI mapping. PLUGINDEX mappedIndex = uint8_max; PlugParamIndex paramIndex = 0; uint16 paramValue = uint16_max; bool captured = sndFile.GetMIDIMapper().OnMIDImsg(midiData, mappedIndex, paramIndex, paramValue); + m_midiCCMapped = (paramValue != uint16_max); - // Handle MIDI messages assigned to shortcuts - CInputHandler *ih = CMainFrame::GetInputHandler(); - if(ih->HandleMIDIMessage(static_cast(kCtxViewPatterns + 1 + m_Cursor.GetColumnType()), midiData) != kcNull - || ih->HandleMIDIMessage(kCtxAllContexts, midiData) != kcNull) - { - // Mapped to a command, no need to pass message on. - captured = true; - } - // Write parameter control commands if needed. if(paramValue != uint16_max && IsEditingEnabled() && sndFile.GetType() == MOD_TYPE_MPT) { @@ -4003,12 +3969,37 @@ pModDoc->UpdateAllViews(this, PatternHint(editPos.pattern).Data(), this); } - if(captured) + // Handle MIDI messages assigned to shortcuts + CInputHandler *ih = CMainFrame::GetInputHandler(); + if(ih->HandleMIDIMessage(static_cast(kCtxViewPatterns + 1 + m_Cursor.GetColumnType()), midiData) != kcNull + || ih->HandleMIDIMessage(kCtxAllContexts, midiData) != kcNull) { - // Event captured by MIDI mapping or shortcut, no need to pass message on. - return 1; + // Mapped to a command, no need to pass message on. + captured = true; } + return captured; +} + + +void CViewPattern::OnMidiMessage(mpt::const_byte_span midiData) +{ + CMainFrame *pMainFrm = CMainFrame::GetMainFrame(); + CModDoc *pModDoc = GetDocument(); + if(pModDoc == nullptr || pMainFrm == nullptr || midiData.empty()) + return; + + CSoundFile &sndFile = pModDoc->GetSoundFile(); + + const uint8 midiByte1 = MIDIEvents::GetDataByte1FromEvent(midiData); + const uint8 midiByte2 = MIDIEvents::GetDataByte2FromEvent(midiData); + const uint8 nNote = midiByte1 + NOTE_MIN; + int vol = midiByte2; + MIDIEvents::EventType event = MIDIEvents::GetTypeFromEvent(midiData); + + if((event == MIDIEvents::evNoteOn) && !midiByte2) + event = MIDIEvents::evNoteOff; //Convert event to note-off if req'd + const FlagSet midiSetup = TrackerSettings::Instance().midiSetup; const RecordPitchBend pitchBendBehaviour = TrackerSettings::Instance().pitchBendBehaviour; const auto &modSpecs = sndFile.GetModSpecifications(); @@ -4016,12 +4007,7 @@ switch(event) { - case MIDIEvents::evNoteOff: // Note Off - if(m_midiSustainActive[channel]) - { - m_midiSustainBuffer[channel].push_back(midiData); - return 1; - } + case MIDIEvents::evNoteOff: // The following method takes care of: // . Silencing specific active notes (just setting nNote to 255 as was done before is not acceptible) // . Entering a note off in pattern if required @@ -4028,7 +4014,7 @@ TempStopNote(nNote, midiSetup[MidiSetup::RecordNoteOff]); break; - case MIDIEvents::evNoteOn: // Note On + case MIDIEvents::evNoteOn: // Continue playing as soon as MIDI notes are being received if((pMainFrm->GetSoundFilePlaying() != &sndFile || sndFile.IsPaused()) && midiSetup[MidiSetup::PlayPatternOnMidiNote]) { @@ -4038,7 +4024,7 @@ pModDoc->OnPatternPlayNoLoop(); } - vol = CMainFrame::ApplyVolumeRelatedSettings(midiData, midiVolume); + vol = CMainFrame::NoteVolumeFromMidi(midiData); if(vol < 0) vol = -1; else @@ -4046,15 +4032,15 @@ TempEnterNote(nNote, vol, true); break; - case MIDIEvents::evPolyAftertouch: // Polyphonic aftertouch - EnterAftertouch(nNote, vol); + case MIDIEvents::evPolyAftertouch: + EnterAftertouch(nNote, midiByte2); break; - case MIDIEvents::evChannelAftertouch: // Channel aftertouch + case MIDIEvents::evChannelAftertouch: EnterAftertouch(NOTE_NONE, midiByte1); break; - case MIDIEvents::evPitchBend: // Pitch wheel + case MIDIEvents::evPitchBend: if(modSpecs.HasCommand(CMD_FINETUNE)) recordParamAsZxx = pitchBendBehaviour != RecordPitchBend::DoNotRecord; else @@ -4061,36 +4047,15 @@ recordParamAsZxx = pitchBendBehaviour == RecordPitchBend::RecordAsFinetuneOrMacro || pitchBendBehaviour == RecordPitchBend::RecordAsMacro; break; - case MIDIEvents::evControllerChange: //Controller change + case MIDIEvents::evControllerChange: // Checking whether to record MIDI controller change as MIDI macro change. // Don't write this if command was already written by MIDI mapping. - if((paramValue == uint16_max || sndFile.GetType() != MOD_TYPE_MPT) + if((!m_midiCCMapped || sndFile.GetType() != MOD_TYPE_MPT) && midiSetup[MidiSetup::RecordCCsAsMacros] && !TrackerSettings::Instance().midiIgnoreCCs.Get()[midiByte1 & 0x7F]) { recordParamAsZxx = true; } - - switch(midiByte1) - { - case MIDIEvents::MIDICC_Volume_Coarse: - midiVolume = midiByte2; - break; - - case MIDIEvents::MIDICC_HoldPedal_OnOff: - m_midiSustainActive[channel] = (midiByte2 >= 0x40); - if(!m_midiSustainActive[channel]) - { - // Release all notes - for(const auto offEvent : m_midiSustainBuffer[channel]) - { - OnMidiMsg(offEvent, 0); - } - m_midiSustainBuffer[channel].clear(); - } - recordParamAsZxx = false; - break; - } break; case MIDIEvents::evSystem: @@ -4097,7 +4062,7 @@ if(midiSetup[MidiSetup::RespondToPlayControl]) { // Respond to MIDI song messages - switch(static_cast(midiData)) + switch(static_cast(midiData[0])) { case MIDIEvents::sysStart: pModDoc->OnPlayerPlayFromStart(); @@ -4179,12 +4144,10 @@ plug->MidiSend(midiData); // Sending MIDI may modify the plugin. For now, if MIDI data // is not active sensing, set modified. - if(midiData != MIDIEvents::System(MIDIEvents::sysActiveSense)) + if(!mpt::span_elements_equal(midiData, MIDIEvents::System(MIDIEvents::sysActiveSense))) pModDoc->SetModified(); } } - - return 1; } Index: mptrack/View_pat.h =================================================================== --- mptrack/View_pat.h (revision 25320) +++ mptrack/View_pat.h (working copy) @@ -14,6 +14,7 @@ #include "openmpt/all/BuildSettings.hpp" #include "Globals.h" +#include "MidiInputCallback.h" #include "PatternCursor.h" #include "Moddoc.h" #include "Mptrack.h" @@ -79,7 +80,7 @@ // Pattern editing class -class CViewPattern final : public CModScrollView +class CViewPattern final : public CModScrollView, public IMidiInputCallback { public: // Pattern status flags @@ -165,9 +166,7 @@ CHANNELINDEX m_chordPatternChannels[MPTChord::notesPerChord]; ModCommand::NOTE m_prevChordNote, m_prevChordBaseNote; - // Note-off event buffer for MIDI sustain pedal - std::array, 16> m_midiSustainBuffer; - std::bitset<16> m_midiSustainActive; + bool m_midiCCMapped = false; struct ChannelState { @@ -359,6 +358,8 @@ LRESULT OnModViewMsg(WPARAM, LPARAM) override; LRESULT OnPlayerNotify(Notification *) override; INT_PTR OnToolHitTest(CPoint point, TOOLINFO *pTI) const override; + bool PreFilterMidiMessage(mpt::const_byte_span midiData) override; + void OnMidiMessage(mpt::const_byte_span midiData) override; //}}AFX_VIRTUAL protected: @@ -453,7 +454,6 @@ afx_msg void OnUpdateUndo(CCmdUI *pCmdUI); afx_msg void OnUpdateRedo(CCmdUI *pCmdUI); afx_msg void OnSelectPlugin(UINT nID); - afx_msg LRESULT OnMidiMsg(WPARAM, LPARAM); afx_msg LRESULT OnRecordPlugParamChange(WPARAM, LPARAM); afx_msg LRESULT OnCustomKeyMsg(WPARAM, LPARAM); afx_msg void OnClearSelectionFromMenu(); Index: mptrack/View_smp.cpp =================================================================== --- mptrack/View_smp.cpp (revision 25320) +++ mptrack/View_smp.cpp (working copy) @@ -154,7 +154,6 @@ ON_COMMAND_RANGE(ID_SAMPLE_CUE_1, ID_SAMPLE_CUE_9, &CViewSample::OnSetCuePoint) ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, &CViewSample::OnUpdateUndo) ON_UPDATE_COMMAND_UI(ID_EDIT_REDO, &CViewSample::OnUpdateRedo) - ON_MESSAGE(WM_MOD_MIDIMSG, &CViewSample::OnMidiMsg) ON_MESSAGE(WM_MOD_KEYCOMMAND, &CViewSample::OnCustomKeyMsg) //rewbs.customKeys //}}AFX_MSG_MAP END_MESSAGE_MAP() @@ -3780,7 +3779,6 @@ } if(dlg.m_editOption == AddSilenceDlg::kResize) { - // resize - dlg.m_nSamples = new size if(dlg.m_numSamples == 0) { pModDoc->GetSampleUndo().PrepareUndo(m_nSample, sundo_replace, "Delete Sample"); @@ -3798,7 +3796,6 @@ } } else { - // add silence - dlg.m_nSamples = amount of bytes to be added if(dlg.m_numSamples > 0) { CriticalSection cs; @@ -3819,80 +3816,44 @@ } } -LRESULT CViewSample::OnMidiMsg(WPARAM midiDataParam, LPARAM) + +bool CViewSample::PreFilterMidiMessage(mpt::const_byte_span midiData) { - const uint32 midiData = static_cast(midiDataParam); - static uint8 midiVolume = 127; + return DefaultPreFilterMidiMessage(midiData, &GetDocument()->GetSoundFile(), kCtxViewSamples); +} + +void CViewSample::OnMidiMessage(mpt::const_byte_span midiData) +{ CModDoc *pModDoc = GetDocument(); + if(!pModDoc || midiData.empty()) + return; + const uint8 midiByte1 = MIDIEvents::GetDataByte1FromEvent(midiData); const uint8 midiByte2 = MIDIEvents::GetDataByte2FromEvent(midiData); - const uint8 channel = MIDIEvents::GetChannelFromEvent(midiData); - CSoundFile *pSndFile = (pModDoc) ? &pModDoc->GetSoundFile() : nullptr; - if (!pSndFile) return 0; + CSoundFile &sndFile = pModDoc->GetSoundFile(); uint8 nNote = midiByte1 + NOTE_MIN; - int nVol = midiByte2; MIDIEvents::EventType event = MIDIEvents::GetTypeFromEvent(midiData); - if(event == MIDIEvents::evNoteOn && !nVol) + if(event == MIDIEvents::evNoteOn && !midiByte2) event = MIDIEvents::evNoteOff; //Convert event to note-off if req'd - // Handle MIDI messages assigned to shortcuts - CInputHandler *ih = CMainFrame::GetInputHandler(); - if(ih->HandleMIDIMessage(kCtxViewSamples, midiData) != kcNull - || ih->HandleMIDIMessage(kCtxAllContexts, midiData) != kcNull) - { - // Mapped to a command, no need to pass message on. - return 1; - } - switch(event) { - case MIDIEvents::evNoteOff: // Note Off - if(m_midiSustainActive[channel]) - { - m_midiSustainBuffer[channel].push_back(midiData); - return 1; - } - [[fallthrough]]; - case MIDIEvents::evNoteOn: // Note On + case MIDIEvents::evNoteOff: + case MIDIEvents::evNoteOn: LimitMax(nNote, NOTE_MAX); pModDoc->NoteOff(nNote, true); if(event != MIDIEvents::evNoteOff) - { - nVol = CMainFrame::ApplyVolumeRelatedSettings(midiData, midiVolume); - PlayNote(nNote, 0, nVol); - } + PlayNote(nNote, 0, CMainFrame::NoteVolumeFromMidi(midiData)); break; - case MIDIEvents::evControllerChange: // Controller change - switch(midiByte1) - { - case MIDIEvents::MIDICC_Volume_Coarse: // Volume - midiVolume = midiByte2; - break; - - case MIDIEvents::MIDICC_HoldPedal_OnOff: - m_midiSustainActive[channel] = (midiByte2 >= 0x40); - if(!m_midiSustainActive[channel]) - { - // Release all notes - for(const auto offEvent : m_midiSustainBuffer[channel]) - { - OnMidiMsg(offEvent, 0); - } - m_midiSustainBuffer[channel].clear(); - } - break; - } - break; - case MIDIEvents::evPitchBend: for(CHANNELINDEX chn : m_noteChannel) { if(chn != CHANNELINDEX_INVALID) - pSndFile->m_PlayState.Chn[chn].SetMIDIPitchBend(midiByte2, midiByte1); + sndFile.m_PlayState.Chn[chn].SetMIDIPitchBend(midiByte2, midiByte1); } break; @@ -3899,10 +3860,9 @@ default: break; } - - return 1; } + BOOL CViewSample::PreTranslateMessage(MSG *pMsg) { if (pMsg) Index: mptrack/View_smp.h =================================================================== --- mptrack/View_smp.h (revision 25320) +++ mptrack/View_smp.h (working copy) @@ -13,6 +13,7 @@ #include "openmpt/all/BuildSettings.hpp" #include "Globals.h" +#include "MidiInputCallback.h" #include "Moddoc.h" #include "TrackerSettings.h" #include "../soundlib/modsmp_ctrl.h" @@ -24,7 +25,7 @@ #define SMP_LEFTBAR_BUTTONS 8 class OPLInstrDlg; -class CViewSample: public CModScrollView +class CViewSample: public CModScrollView, public IMidiInputCallback { public: enum Flags : uint8 @@ -101,10 +102,6 @@ CPoint m_lastDrawPoint{-1, -1}; // For drawing horizontal lines int m_drawChannel = 0; // Which sample channel are we drawing on? - // Note-off event buffer for MIDI sustain pedal - std::array, 16> m_midiSustainBuffer; - std::bitset<16> m_midiSustainActive; - DWORD m_NcButtonState[SMP_LEFTBAR_BUTTONS]; std::array m_dwNotifyPos; CModDoc::NoteToChannelMap m_noteChannel; // Note -> Preview channel assignment @@ -197,6 +194,8 @@ BOOL PreTranslateMessage(MSG *pMsg) override; BOOL OnScrollBy(CSize sizeScroll, BOOL bDoScroll = TRUE) override; INT_PTR OnToolHitTest(CPoint point, TOOLINFO *pTI) const override; + bool PreFilterMidiMessage(mpt::const_byte_span midiData) override; + void OnMidiMessage(mpt::const_byte_span midiData) override; //}}AFX_VIRTUAL protected: @@ -257,7 +256,6 @@ afx_msg void OnAddSilence(); afx_msg void OnChangeGridSize(); afx_msg void OnQuickFade(); - afx_msg LRESULT OnMidiMsg(WPARAM, LPARAM); afx_msg LRESULT OnCustomKeyMsg(WPARAM, LPARAM); //rewbs.customKeys // cppcheck-suppress duplInheritedMember afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt); Index: mptrack/View_tre.cpp =================================================================== --- mptrack/View_tre.cpp (revision 25320) +++ mptrack/View_tre.cpp (working copy) @@ -157,7 +157,6 @@ ON_COMMAND(ID_MODTREE_SORT_BY_SIZE, &CModTree::OnSortBySize) ON_MESSAGE(WM_MOD_KEYCOMMAND, &CModTree::OnCustomKeyMsg) - ON_MESSAGE(WM_MOD_MIDIMSG, &CModTree::OnMidiMsg) //}}AFX_MSG_MAP END_MESSAGE_MAP() @@ -264,10 +263,8 @@ void CModTree::OnDestroy() { - if(CMainFrame::GetMainFrame()->GetMidiRecordWnd() == m_hWnd) - { - CMainFrame::GetMainFrame()->SetMidiRecordWnd(nullptr); - } + if(CMainFrame::GetMainFrame()->GetMidiRecordCallback() == this) + CMainFrame::GetMainFrame()->SetMidiRecordCallback(nullptr); } @@ -4467,31 +4464,32 @@ } -LRESULT CModTree::OnMidiMsg(WPARAM midiData_, LPARAM) +bool CModTree::PreFilterMidiMessage(mpt::const_byte_span midiData) { - uint32 midiData = static_cast(midiData_); - // Handle MIDI messages assigned to shortcuts - CInputHandler *ih = CMainFrame::GetInputHandler(); - if(ih->HandleMIDIMessage(kCtxViewTree, midiData) == kcNull) - ih->HandleMIDIMessage(kCtxAllContexts, midiData); + return DefaultPreFilterMidiMessage(midiData, nullptr, kCtxViewTree); +} - uint8 midiByte1 = MIDIEvents::GetDataByte1FromEvent(midiData); - int volume; + +void CModTree::OnMidiMessage(mpt::const_byte_span midiData) +{ + if(midiData.empty()) + return; + switch(MIDIEvents::GetTypeFromEvent(midiData)) { case MIDIEvents::evNoteOn: - volume = MIDIEvents::GetDataByte2FromEvent(midiData); - if(volume > 0) + if(uint8 volume = MIDIEvents::GetDataByte2FromEvent(midiData); volume > 0) { - PlayItem(GetSelectedItem(), midiByte1 + NOTE_MIN, Util::muldivr(volume, 256, 127)); - return 1; + uint8 note = MIDIEvents::GetDataByte1FromEvent(midiData); + PlayItem(GetSelectedItem(), note + NOTE_MIN, CMainFrame::NoteVolumeFromMidi(midiData)); + return; } [[fallthrough]]; case MIDIEvents::evNoteOff: PlayItem(GetSelectedItem(), NOTE_NOTECUT); - return 1; + break; default: - return 0; + break; } } @@ -4506,8 +4504,8 @@ CTreeCtrl::OnKillFocus(pNewWnd); CMainFrame::GetMainFrame()->m_bModTreeHasFocus = false; // Required to immediately redirect MIDI input focus after drag&drop from tree view to editor - if(pNewWnd != nullptr) - CMainFrame::GetMainFrame()->SetMidiRecordWnd(pNewWnd->m_hWnd); + if(auto *callback = dynamic_cast(pNewWnd)) + CMainFrame::GetMainFrame()->SetMidiRecordCallback(callback); } @@ -4515,7 +4513,7 @@ { CTreeCtrl::OnSetFocus(pOldWnd); CMainFrame::GetMainFrame()->m_bModTreeHasFocus = true; - CMainFrame::GetMainFrame()->SetMidiRecordWnd(m_hWnd); + CMainFrame::GetMainFrame()->SetMidiRecordCallback(this); } Index: mptrack/View_tre.h =================================================================== --- mptrack/View_tre.h (revision 25320) +++ mptrack/View_tre.h (working copy) @@ -12,6 +12,7 @@ #pragma once #include "openmpt/all/BuildSettings.hpp" +#include "MidiInputCallback.h" #include "../soundlib/modcommand.h" #include "../soundlib/Snd_defs.h" @@ -65,7 +66,7 @@ }; -class CModTree: public CTreeCtrl +class CModTree: public CTreeCtrl, public IMidiInputCallback { public: enum ModItemType : uint8 @@ -267,6 +268,8 @@ //{{AFX_VIRTUAL(CModTree) public: BOOL PreTranslateMessage(MSG *pMsg) override; + bool PreFilterMidiMessage(mpt::const_byte_span midiData) override; + void OnMidiMessage(mpt::const_byte_span midiData) override; //}}AFX_VIRTUAL // Drag & Drop operations @@ -373,7 +376,6 @@ afx_msg void OnSortBySize() { SetInstrumentLibraryFilterSortOrder(LibrarySortOrder::Size); } afx_msg LRESULT OnCustomKeyMsg(WPARAM, LPARAM); - LRESULT OnMidiMsg(WPARAM midiData, LPARAM); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; Index: mptrack/Vstplug.cpp =================================================================== --- mptrack/Vstplug.cpp (revision 25320) +++ mptrack/Vstplug.cpp (working copy) @@ -1504,11 +1504,13 @@ { if(ev->type == kVstMidiType) { - plugin->MidiSend(static_cast(ev)->midiData); + VstMidiEvent *event = static_cast(ev); + size_t size = MIDIEvents::GetEventLength(event->midiData[0]); + plugin->MidiSend(mpt::as_span(event->midiData).subspan(0, size)); } else if(ev->type == kVstSysExType) { auto event = static_cast(ev); - plugin->MidiSend(mpt::as_span(mpt::byte_cast(event->sysexDump), event->dumpBytes)); + plugin->MidiSend(mpt::as_span(event->sysexDump, event->dumpBytes)); } } } @@ -1523,7 +1525,12 @@ if(ev->type == kVstMidiType) { VstMidiEvent *event = static_cast(ev); - ::SendNotifyMessage(CMainFrame::GetMainFrame()->GetMidiRecordWnd(), WM_MOD_MIDIMSG, event->midiData, reinterpret_cast(this)); + size_t size = MIDIEvents::GetEventLength(event->midiData[0]); + CMainFrame::GetMainFrame()->EnqueueMidiMessage(mpt::as_span(event->midiData).subspan(0, size), this); + } else if (ev->type == kVstSysExType) + { + VstMidiSysexEvent *event = static_cast(ev); + CMainFrame::GetMainFrame()->EnqueueMidiMessage(mpt::as_span(event->sysexDump, event->dumpBytes), this); } } } @@ -1621,12 +1628,11 @@ bool CVstPlugin::MidiSend(mpt::const_byte_span midiData) { - if(IsBypassed()) + if(IsBypassed() || midiData.empty()) return true; ResetSilence(); - const uint8 type = mpt::byte_cast(midiData[0]); - if(type == MIDIEvents::sysExStart) + if(midiData[0] == std::byte{MIDIEvents::sysExStart}) { VstMidiSysexEvent event{}; event.type = kVstSysExType; @@ -1642,13 +1648,13 @@ // C-5 01|=== .. // TODO: Should not be used with real-time notes! Letting the key go too quickly // (e.g. while output device is being initialized) will cause the note to be stuck! - bool insertAtFront = (MIDIEvents::GetTypeFromEvent(type) == MIDIEvents::evNoteOff); + bool insertAtFront = (MIDIEvents::GetTypeFromEvent(midiData) == MIDIEvents::evNoteOff); VstMidiEvent event{}; event.type = kVstMidiType; event.byteSize = sizeof(event); - MPT_ASSERT(midiData.size() <= sizeof(event.midiData) && midiData.size() == MIDIEvents::GetEventLength(type)); - memcpy(&event.midiData, midiData.data(), std::min(sizeof(event.midiData), midiData.size())); + MPT_ASSERT(midiData.size() <= event.midiData.size() && midiData.size() == MIDIEvents::GetEventLength(midiData[0])); + std::copy(midiData.data(), midiData.data() + std::min(event.midiData.size(), midiData.size()), event.midiData.begin()); return vstEvents.Enqueue(&event, insertAtFront); } Index: soundlib/MIDIEvents.cpp =================================================================== --- soundlib/MIDIEvents.cpp (revision 25320) +++ soundlib/MIDIEvents.cpp (working copy) @@ -17,16 +17,30 @@ { // Build a generic MIDI event -uint32 Event(EventType eventType, uint8 midiChannel, uint8 dataByte1, uint8 dataByte2) +std::array Event(EventType eventType, uint8 midiChannel, uint8 dataByte1, uint8 dataByte2) { MPT_ASSERT(!(dataByte1 & 0x80)); MPT_ASSERT(!(dataByte2 & 0x80)); - return (eventType & 0xF0) | (midiChannel & 0x0F) | (dataByte1 << 8) | (dataByte2 << 16); + return { + mpt::byte_cast(static_cast((eventType & 0xF0) | (midiChannel & 0x0F))), + mpt::byte_cast(dataByte1), + mpt::byte_cast(dataByte2) + }; } +std::array Event(EventType eventType, uint8 midiChannel, uint8 dataByte) +{ + MPT_ASSERT(!(dataByte & 0x80)); + return { + mpt::byte_cast(static_cast((eventType & 0xF0) | (midiChannel & 0x0F))), + mpt::byte_cast(dataByte) + }; +} + + // Build a MIDI CC event -uint32 CC(MidiCC midiCC, uint8 midiChannel, uint8 param) +std::array CC(MidiCC midiCC, uint8 midiChannel, uint8 param) { return Event(evControllerChange, midiChannel, static_cast(midiCC), param); } @@ -33,7 +47,7 @@ // Build a MIDI Pitchbend event -uint32 PitchBend(uint8 midiChannel, uint16 bendAmount) +std::array PitchBend(uint8 midiChannel, uint16 bendAmount) { MPT_ASSERT(!(bendAmount & 0xC000)); return Event(evPitchBend, midiChannel, static_cast(bendAmount & 0x7F), static_cast((bendAmount >> 7) & 0x7F)); @@ -41,14 +55,14 @@ // Build a MIDI Program Change event -uint32 ProgramChange(uint8 midiChannel, uint8 program) +std::array ProgramChange(uint8 midiChannel, uint8 program) { - return Event(evProgramChange, midiChannel, program, 0); + return Event(evProgramChange, midiChannel, program); } // Build a MIDI Note Off event -uint32 NoteOff(uint8 midiChannel, uint8 note, uint8 velocity) +std::array NoteOff(uint8 midiChannel, uint8 note, uint8 velocity) { return Event(evNoteOff, midiChannel, note, velocity); } @@ -55,7 +69,7 @@ // Build a MIDI Note On event -uint32 NoteOn(uint8 midiChannel, uint8 note, uint8 velocity) +std::array NoteOn(uint8 midiChannel, uint8 note, uint8 velocity) { return Event(evNoteOn, midiChannel, note, velocity); } @@ -62,14 +76,14 @@ // Build a MIDI System Event -uint8 System(SystemEvent eventType) +std::array System(SystemEvent eventType) { - return static_cast(eventType); + return {mpt::byte_cast(static_cast(eventType))}; } // Build a MIDI Song Position Event -uint32 SongPosition(uint16 quarterNotes) +std::array SongPosition(uint16 quarterNotes) { MPT_ASSERT(!(quarterNotes & 0xC000)); return Event(evSystem, sysPositionPointer, static_cast(quarterNotes & 0x7F), static_cast((quarterNotes >> 7) & 0x7F)); @@ -77,38 +91,39 @@ // Get MIDI channel from a MIDI event -uint8 GetChannelFromEvent(uint32 midiMsg) +uint8 GetChannelFromEvent(mpt::const_byte_span midiMsg) { - return static_cast((midiMsg & 0xF)); + MPT_ASSERT(midiMsg.size() >= 1); + return static_cast((mpt::byte_cast(midiMsg[0]) & 0x0F)); } // Get MIDI Event type from a MIDI event -EventType GetTypeFromEvent(uint32 midiMsg) +EventType GetTypeFromEvent(mpt::const_byte_span midiMsg) { - return static_cast(midiMsg & 0xF0); + MPT_ASSERT(midiMsg.size() >= 1); + return static_cast((mpt::byte_cast(midiMsg[0]) & 0xF0)); } // Get first data byte from a MIDI event -uint8 GetDataByte1FromEvent(uint32 midiMsg) +uint8 GetDataByte1FromEvent(mpt::const_byte_span midiMsg) { - return static_cast(((midiMsg >> 8) & 0xFF)); + return midiMsg.size() >= 2 ? mpt::byte_cast(midiMsg[1]) : uint8{0}; } - // Get second data byte from a MIDI event -uint8 GetDataByte2FromEvent(uint32 midiMsg) +uint8 GetDataByte2FromEvent(mpt::const_byte_span midiMsg) { - return static_cast(((midiMsg >> 16) & 0xFF)); + return midiMsg.size() >= 3 ? mpt::byte_cast(midiMsg[2]) : uint8{0}; } // Get the length of a MIDI event in bytes -uint8 GetEventLength(uint8 firstByte) +uint8 GetEventLength(std::byte firstByte) { uint8 msgSize = 3; - switch(GetTypeFromEvent(firstByte)) + switch(static_cast((mpt::byte_cast(firstByte) & 0xF0))) { case evProgramChange: case evChannelAftertouch: @@ -115,7 +130,7 @@ msgSize = 2; break; case evSystem: - switch(static_cast(firstByte)) + switch(static_cast(mpt::byte_cast(firstByte))) { case sysQuarterFrame: case sysSongSelect: Index: soundlib/MIDIEvents.h =================================================================== --- soundlib/MIDIEvents.h (revision 25320) +++ soundlib/MIDIEvents.h (working copy) @@ -138,34 +138,35 @@ extern const char* const MidiCCNames[MIDICC_end + 1]; // Charset::UTF8 // Build a generic MIDI event - uint32 Event(EventType eventType, uint8 midiChannel, uint8 dataByte1, uint8 dataByte2); + std::array Event(EventType eventType, uint8 midiChannel, uint8 dataByte1, uint8 dataByte2); + std::array Event(EventType eventType, uint8 midiChannel, uint8 dataByte1); // Build a MIDI CC event - uint32 CC(MidiCC midiCC, uint8 midiChannel, uint8 param); + std::array CC(MidiCC midiCC, uint8 midiChannel, uint8 param); // Build a MIDI Pitchbend event - uint32 PitchBend(uint8 midiChannel, uint16 bendAmount); + std::array PitchBend(uint8 midiChannel, uint16 bendAmount); // Build a MIDI Program Change event - uint32 ProgramChange(uint8 midiChannel, uint8 program); + std::array ProgramChange(uint8 midiChannel, uint8 program); // Build a MIDI Note Off event - uint32 NoteOff(uint8 midiChannel, uint8 note, uint8 velocity); + std::array NoteOff(uint8 midiChannel, uint8 note, uint8 velocity); // Build a MIDI Note On event - uint32 NoteOn(uint8 midiChannel, uint8 note, uint8 velocity); + std::array NoteOn(uint8 midiChannel, uint8 note, uint8 velocity); // Build a MIDI System Event - uint8 System(SystemEvent eventType); + std::array System(SystemEvent eventType); // Build a MIDI Song Position Event - uint32 SongPosition(uint16 quarterNotes); + std::array SongPosition(uint16 quarterNotes); // Get MIDI channel from a MIDI event - uint8 GetChannelFromEvent(uint32 midiMsg); + uint8 GetChannelFromEvent(mpt::const_byte_span midiMsg); // Get MIDI Event type from a MIDI event - EventType GetTypeFromEvent(uint32 midiMsg); + EventType GetTypeFromEvent(mpt::const_byte_span midiMsg); // Get first data byte from a MIDI event - uint8 GetDataByte1FromEvent(uint32 midiMsg); + uint8 GetDataByte1FromEvent(mpt::const_byte_span midiMsg); // Get second data byte from a MIDI event - uint8 GetDataByte2FromEvent(uint32 midiMsg); + uint8 GetDataByte2FromEvent(mpt::const_byte_span midiMsg); // Get the length of a MIDI event in bytes - uint8 GetEventLength(uint8 firstByte); - + uint8 GetEventLength(std::byte firstByte); + inline uint8 GetEventLength(uint8 firstByte) { return GetEventLength(mpt::byte_cast(firstByte)); } } Index: soundlib/plugins/PlugInterface.cpp =================================================================== --- soundlib/plugins/PlugInterface.cpp (revision 25320) +++ soundlib/plugins/PlugInterface.cpp (working copy) @@ -377,14 +377,6 @@ } -bool IMixPlugin::MidiSend(uint32 midiCode) -{ - std::array midiData; - memcpy(midiData.data(), &midiCode, 4); - return MidiSend(mpt::as_span(midiData.data(), std::min(static_cast(midiData.size()), MIDIEvents::GetEventLength(mpt::byte_cast(midiData[0]))))); -} - - // Get list of plugins to which output is sent. A nullptr indicates master output. size_t IMixPlugin::GetOutputPlugList(std::vector &list) { @@ -1033,12 +1025,10 @@ } #ifdef MODPLUG_TRACKER - if(m_recordMIDIOut && midiData[0] != std::byte{0xF0}) + if(m_recordMIDIOut) { // Spam MIDI data to all views - uint32 midiCode = 0; - memcpy(&midiCode, midiData.data(), std::min(sizeof(midiCode), midiData.size())); - ::PostMessage(CMainFrame::GetMainFrame()->GetMidiRecordWnd(), WM_MOD_MIDIMSG, midiCode, reinterpret_cast(this)); + CMainFrame::GetMainFrame()->EnqueueMidiMessage(midiData, this); } #endif // MODPLUG_TRACKER } Index: soundlib/plugins/PlugInterface.h =================================================================== --- soundlib/plugins/PlugInterface.h (revision 25320) +++ soundlib/plugins/PlugInterface.h (working copy) @@ -148,7 +148,6 @@ virtual float RenderSilence(uint32 numSamples); // MIDI event handling - bool MidiSend(uint32 midiCode); virtual bool MidiSend(mpt::const_byte_span /*midiData*/) { return true; } virtual void MidiCC(MIDIEvents::MidiCC /*nController*/, uint8 /*nParam*/, CHANNELINDEX /*trackChannel*/) { } virtual void MidiPitchBendRaw(int32 /*pitchbend*/, CHANNELINDEX /*trackChannel*/) {} Index: soundlib/Sndfile.h =================================================================== --- soundlib/Sndfile.h (revision 25320) +++ soundlib/Sndfile.h (working copy) @@ -267,6 +267,7 @@ int64 StreamFrames = 0; uint64 SystemTimestamp = 0; // nanoseconds double Speed = 1.0; + uint32 FramesRenderedInChunk = 0; }; Index: soundlib/Sndmix.cpp =================================================================== --- soundlib/Sndmix.cpp (revision 25320) +++ soundlib/Sndmix.cpp (working copy) @@ -364,6 +364,7 @@ countToRender -= countChunk; m_PlayState.m_nBufferCount -= countChunk; m_PlayState.m_lTotalSampleCount += countChunk; + m_TimingInfo.FramesRenderedInChunk += countChunk; const ROWINDEX rowsPerBeat = m_PlayState.m_nCurrentRowsPerBeat ? m_PlayState.m_nCurrentRowsPerBeat : DEFAULT_ROWS_PER_BEAT; if(!m_PlayState.m_nBufferCount && !m_PlayState.m_flags[SONG_PAUSED]) m_PlayState.m_ppqPosFract += 1.0 / (rowsPerBeat * m_PlayState.TicksOnRow()); Index: test/test.cpp =================================================================== --- test/test.cpp (revision 25320) +++ test/test.cpp (working copy) @@ -3396,44 +3396,42 @@ // Test MIDI Event generating / reading MPT_ATTR_NOINLINE MPT_DECL_NOINLINE static void TestMIDIEvents() { - uint32 midiEvent; + auto ccEvent = MIDIEvents::CC(MIDIEvents::MIDICC_Balance_Coarse, 13, 40); + VERIFY_EQUAL_NONCONT(MIDIEvents::GetTypeFromEvent(ccEvent), MIDIEvents::evControllerChange); + VERIFY_EQUAL_NONCONT(MIDIEvents::GetChannelFromEvent(ccEvent), 13); + VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte1FromEvent(ccEvent), MIDIEvents::MIDICC_Balance_Coarse); + VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte2FromEvent(ccEvent), 40); - midiEvent = MIDIEvents::CC(MIDIEvents::MIDICC_Balance_Coarse, 13, 40); - VERIFY_EQUAL_NONCONT(MIDIEvents::GetTypeFromEvent(midiEvent), MIDIEvents::evControllerChange); - VERIFY_EQUAL_NONCONT(MIDIEvents::GetChannelFromEvent(midiEvent), 13); - VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte1FromEvent(midiEvent), MIDIEvents::MIDICC_Balance_Coarse); - VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte2FromEvent(midiEvent), 40); + auto noteOnEvent = MIDIEvents::NoteOn(10, 50, 120); + VERIFY_EQUAL_NONCONT(MIDIEvents::GetTypeFromEvent(noteOnEvent), MIDIEvents::evNoteOn); + VERIFY_EQUAL_NONCONT(MIDIEvents::GetChannelFromEvent(noteOnEvent), 10); + VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte1FromEvent(noteOnEvent), 50); + VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte2FromEvent(noteOnEvent), 120); - midiEvent = MIDIEvents::NoteOn(10, 50, 120); - VERIFY_EQUAL_NONCONT(MIDIEvents::GetTypeFromEvent(midiEvent), MIDIEvents::evNoteOn); - VERIFY_EQUAL_NONCONT(MIDIEvents::GetChannelFromEvent(midiEvent), 10); - VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte1FromEvent(midiEvent), 50); - VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte2FromEvent(midiEvent), 120); + auto noteOffEvent = MIDIEvents::NoteOff(15, 127, 42); + VERIFY_EQUAL_NONCONT(MIDIEvents::GetTypeFromEvent(noteOffEvent), MIDIEvents::evNoteOff); + VERIFY_EQUAL_NONCONT(MIDIEvents::GetChannelFromEvent(noteOffEvent), 15); + VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte1FromEvent(noteOffEvent), 127); + VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte2FromEvent(noteOffEvent), 42); - midiEvent = MIDIEvents::NoteOff(15, 127, 42); - VERIFY_EQUAL_NONCONT(MIDIEvents::GetTypeFromEvent(midiEvent), MIDIEvents::evNoteOff); - VERIFY_EQUAL_NONCONT(MIDIEvents::GetChannelFromEvent(midiEvent), 15); - VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte1FromEvent(midiEvent), 127); - VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte2FromEvent(midiEvent), 42); + auto progEvent = MIDIEvents::ProgramChange(1, 127); + VERIFY_EQUAL_NONCONT(MIDIEvents::GetTypeFromEvent(progEvent), MIDIEvents::evProgramChange); + VERIFY_EQUAL_NONCONT(MIDIEvents::GetChannelFromEvent(progEvent), 1); + VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte1FromEvent(progEvent), 127); + VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte2FromEvent(progEvent), 0); - midiEvent = MIDIEvents::ProgramChange(1, 127); - VERIFY_EQUAL_NONCONT(MIDIEvents::GetTypeFromEvent(midiEvent), MIDIEvents::evProgramChange); - VERIFY_EQUAL_NONCONT(MIDIEvents::GetChannelFromEvent(midiEvent), 1); - VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte1FromEvent(midiEvent), 127); - VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte2FromEvent(midiEvent), 0); + auto pbEvent = MIDIEvents::PitchBend(2, MIDIEvents::pitchBendMax); + VERIFY_EQUAL_NONCONT(MIDIEvents::GetTypeFromEvent(pbEvent), MIDIEvents::evPitchBend); + VERIFY_EQUAL_NONCONT(MIDIEvents::GetChannelFromEvent(pbEvent), 2); + VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte1FromEvent(pbEvent), 0x7F); + VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte2FromEvent(pbEvent), 0x7F); - midiEvent = MIDIEvents::PitchBend(2, MIDIEvents::pitchBendMax); - VERIFY_EQUAL_NONCONT(MIDIEvents::GetTypeFromEvent(midiEvent), MIDIEvents::evPitchBend); - VERIFY_EQUAL_NONCONT(MIDIEvents::GetChannelFromEvent(midiEvent), 2); - VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte1FromEvent(midiEvent), 0x7F); - VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte2FromEvent(midiEvent), 0x7F); - - midiEvent = MIDIEvents::System(MIDIEvents::sysStart); - VERIFY_EQUAL_NONCONT(MIDIEvents::GetTypeFromEvent(midiEvent), MIDIEvents::evSystem); - VERIFY_EQUAL_NONCONT(midiEvent & 0xFF, MIDIEvents::sysStart); - VERIFY_EQUAL_NONCONT(MIDIEvents::GetChannelFromEvent(midiEvent), MIDIEvents::sysStart & 0x0F); - VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte1FromEvent(midiEvent), 0); - VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte2FromEvent(midiEvent), 0); + auto sysEvent = MIDIEvents::System(MIDIEvents::sysStart); + VERIFY_EQUAL_NONCONT(MIDIEvents::GetTypeFromEvent(sysEvent), MIDIEvents::evSystem); + VERIFY_EQUAL_NONCONT(sysEvent[0], std::byte{MIDIEvents::sysStart}); + VERIFY_EQUAL_NONCONT(MIDIEvents::GetChannelFromEvent(sysEvent), MIDIEvents::sysStart & 0x0F); + VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte1FromEvent(sysEvent), 0); + VERIFY_EQUAL_NONCONT(MIDIEvents::GetDataByte2FromEvent(sysEvent), 0); }