initial wip emscripten port
This commit is contained in:
parent
22ce8ac538
commit
d63e081b7a
|
@ -14,8 +14,21 @@ if(WIN32)
|
||||||
set(SDL2_MIXER_PATH "${CMAKE_CURRENT_LIST_DIR}/Libs/SDL2_mixer")
|
set(SDL2_MIXER_PATH "${CMAKE_CURRENT_LIST_DIR}/Libs/SDL2_mixer")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(${CMAKE_SYSTEM_NAME} MATCHES "Emscripten")
|
||||||
|
set(USE_FLAGS "-s USE_SDL=2 -s USE_SDL_MIXER=2")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${USE_FLAGS}")
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${USE_FLAGS}")
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${USE_FLAGS}")
|
||||||
|
set(CMAKE_EXECUTABLE_SUFFIX .html)
|
||||||
|
|
||||||
|
set(SDL2_FOUND TRUE)
|
||||||
|
set(SDL2_INCLUDE_DIR "${EMSCRIPTEN_ROOT_PATH}/system/include/SDL2/")
|
||||||
|
set(SDL2_MIXER_INCLUDE_DIR "${EMSCRIPTEN_ROOT_PATH}/system/include/SDL2/")
|
||||||
|
set(SDL2_LIBRARIES "nul")
|
||||||
|
else()
|
||||||
find_package(SDL2 REQUIRED)
|
find_package(SDL2 REQUIRED)
|
||||||
FIND_PACKAGE(SDL2_mixer REQUIRED)
|
find_package(SDL2_mixer REQUIRED)
|
||||||
|
endif()
|
||||||
|
|
||||||
include_directories(${SDL2_INCLUDE_DIR} ${SDL2_MIXER_INCLUDE_DIR})
|
include_directories(${SDL2_INCLUDE_DIR} ${SDL2_MIXER_INCLUDE_DIR})
|
||||||
get_property(dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES)
|
get_property(dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES)
|
||||||
|
@ -23,8 +36,6 @@ foreach(dir ${dirs})
|
||||||
message(STATUS "Include dir='${dir}'")
|
message(STATUS "Include dir='${dir}'")
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
set(SOURCE_FILES
|
set(SOURCE_FILES
|
||||||
SpaceCadetPinball/control.cpp
|
SpaceCadetPinball/control.cpp
|
||||||
SpaceCadetPinball/control.h
|
SpaceCadetPinball/control.h
|
||||||
|
@ -164,6 +175,10 @@ set(SOURCE_FILES
|
||||||
SpaceCadetPinball/imstb_textedit.h
|
SpaceCadetPinball/imstb_textedit.h
|
||||||
SpaceCadetPinball/imstb_rectpack.h
|
SpaceCadetPinball/imstb_rectpack.h
|
||||||
SpaceCadetPinball/imstb_truetype.h
|
SpaceCadetPinball/imstb_truetype.h
|
||||||
|
SpaceCadetPinball/gm_sf2.cpp
|
||||||
|
SpaceCadetPinball/tsf.cpp
|
||||||
|
SpaceCadetPinball/tml.h
|
||||||
|
SpaceCadetPinball/tsf.h
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(SpaceCadetPinball ${SOURCE_FILES})
|
add_executable(SpaceCadetPinball ${SOURCE_FILES})
|
||||||
|
@ -185,6 +200,15 @@ target_precompile_headers(SpaceCadetPinball
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(SpaceCadetPinball ${SDL2_LIBRARY} ${SDL2_MIXER_LIBRARY})
|
target_link_libraries(SpaceCadetPinball ${SDL2_LIBRARY} ${SDL2_MIXER_LIBRARY})
|
||||||
|
target_compile_definitions(SpaceCadetPinball PRIVATE -DMUSIC_TSF)
|
||||||
|
|
||||||
|
if(${CMAKE_SYSTEM_NAME} MATCHES "Emscripten")
|
||||||
|
target_link_libraries(SpaceCadetPinball idbfs.js)
|
||||||
|
set_target_properties(SpaceCadetPinball PROPERTIES LINK_FLAGS
|
||||||
|
"-s ALLOW_MEMORY_GROWTH=1 -s FORCE_FILESYSTEM=1 -s DEMANGLE_SUPPORT=1 \
|
||||||
|
--shell-file ${CMAKE_CURRENT_SOURCE_DIR}/SpaceCadetPinball/emscripten_shell.html \
|
||||||
|
--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/game_resources@game_resources --bind")
|
||||||
|
endif()
|
||||||
|
|
||||||
# On Windows, copy DLL to output
|
# On Windows, copy DLL to output
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
|
|
|
@ -12,8 +12,13 @@ int Sound::Init(int voices)
|
||||||
channelCount = 8;
|
channelCount = 8;
|
||||||
num_channels = channelCount;
|
num_channels = channelCount;
|
||||||
|
|
||||||
auto init = Mix_Init(MIX_INIT_MID);
|
int flags = 0;
|
||||||
return Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, 2, 1024);
|
#ifdef MUSIC_SDL
|
||||||
|
flags |= MIX_INIT_MID;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto init = Mix_Init(flags);
|
||||||
|
return Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, AUDIO_S16LSB, 2, 1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sound::Enable(int channelFrom, int channelTo, int enableFlag)
|
void Sound::Enable(int channelFrom, int channelTo, int enableFlag)
|
||||||
|
|
|
@ -0,0 +1,324 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en-us">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<title>3D Pinball for Windows - Space Cadet</title>
|
||||||
|
<style>
|
||||||
|
/* Based on https://tpenguinltg.github.io/winclassic/ */
|
||||||
|
:root {
|
||||||
|
--ActiveBorder: rgb(212, 208, 200);
|
||||||
|
--ActiveTitle: rgb(10, 36, 106);
|
||||||
|
--AppWorkspace: rgb(128, 128, 128);
|
||||||
|
--Background: rgb(58, 110, 165);
|
||||||
|
--ButtonAlternateFace: rgb(192, 192, 192);
|
||||||
|
--ButtonDkShadow: rgb(64, 64, 64);
|
||||||
|
--ButtonFace: rgb(212, 208, 200);
|
||||||
|
--ButtonHilight: rgb(255, 255, 255);
|
||||||
|
--ButtonLight: rgb(212, 208, 200);
|
||||||
|
--ButtonShadow: rgb(128, 128, 128);
|
||||||
|
--ButtonText: rgb(0, 0, 0);
|
||||||
|
--GradientActiveTitle: rgb(166, 202, 240);
|
||||||
|
--GradientInactiveTitle: rgb(192, 192, 192);
|
||||||
|
--GrayText: rgb(128, 128, 128);
|
||||||
|
--Hilight: rgb(10, 36, 106);
|
||||||
|
--HilightText: rgb(255, 255, 255);
|
||||||
|
--HotTrackingColor: rgb(0, 0, 128);
|
||||||
|
--InactiveBorder: rgb(212, 208, 200);
|
||||||
|
--InactiveTitle: rgb(128, 128, 128);
|
||||||
|
--InactiveTitleText: rgb(212, 208, 200);
|
||||||
|
--InfoText: rgb(0, 0, 0);
|
||||||
|
--InfoWindow: rgb(255, 255, 225);
|
||||||
|
--Menu: rgb(212, 208, 200);
|
||||||
|
--MenuBar: rgb(192, 192, 192);
|
||||||
|
--MenuHilight: rgb(0, 0, 128);
|
||||||
|
--MenuText: rgb(0, 0, 0);
|
||||||
|
--Scrollbar: rgb(212, 208, 200);
|
||||||
|
--TitleText: rgb(255, 255, 255);
|
||||||
|
--Window: rgb(255, 255, 255);
|
||||||
|
--WindowFrame: rgb(0, 0, 0);
|
||||||
|
--WindowText: rgb(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background-color: var(--Background);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.emscripten {
|
||||||
|
font-family: monospace;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.emscripten {
|
||||||
|
border: 0px none;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titlebar {
|
||||||
|
text-align: start;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 1px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titlebar .titlebar-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
padding: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titlebar .titlebar-title {
|
||||||
|
display: flex;
|
||||||
|
padding: 0px 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
flex-grow: 1;
|
||||||
|
font-weight: bold;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titlebar .titlebar-wincontrols {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 1px;
|
||||||
|
min-width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titlebar-wincontrols .buttons-wrapper {
|
||||||
|
display: inline-block;
|
||||||
|
width: auto;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titlebar-wincontrols .spacer {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
width: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titlebar-wincontrols .button {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 12px;
|
||||||
|
min-height: 10px;
|
||||||
|
width: 12px;
|
||||||
|
height: 10px;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window {
|
||||||
|
font-size: 8pt;
|
||||||
|
color: var(--WindowText);
|
||||||
|
background-color: var(--ButtonFace);
|
||||||
|
border: 1px solid var(--ActiveBorder);
|
||||||
|
box-shadow: -0.5px -0.5px 0px 0.5px var(--ButtonHilight), 0px 0px 0px 1px var(--ButtonShadow), -0.5px -0.5px 0px 1.5px var(--ButtonLight), 0px 0px 0px 2px var(--ButtonDkShadow);
|
||||||
|
padding-right: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window.active {
|
||||||
|
border: 1px solid var(--ActiveBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
.window.active .titlebar .titlebar-icon {
|
||||||
|
background-color: var(--ActiveTitle);
|
||||||
|
color: var(--TitleText);
|
||||||
|
}
|
||||||
|
|
||||||
|
.window.active .titlebar .titlebar-title {
|
||||||
|
background-color: var(--ActiveTitle);
|
||||||
|
background-image: linear-gradient(to right, var(--ActiveTitle), var(--GradientActiveTitle));
|
||||||
|
color: var(--TitleText);
|
||||||
|
}
|
||||||
|
|
||||||
|
.window.active .titlebar .titlebar-wincontrols,
|
||||||
|
.window.active .titlebar .titlebar-wincontrols .buttons-wrapper {
|
||||||
|
background-color: var(--GradientActiveTitle);
|
||||||
|
font-size: 8pt;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.button {
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button span.button-content {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:active .button-content {
|
||||||
|
transform: translate(1px, 1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
background-color: var(--ButtonFace);
|
||||||
|
color: var(--ButtonText);
|
||||||
|
box-shadow: -0.5px -0.5px 0px 0.5px var(--ButtonLight), 0px 0px 0px 1px var(--ButtonShadow), -0.5px -0.5px 0px 1.5px var(--ButtonHilight), 0px 0px 0px 2px var(--ButtonDkShadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:active {
|
||||||
|
box-shadow: -0.5px -0.5px 0px 0.5px var(--ButtonShadow), 0px 0px 0px 1px var(--ButtonShadow), -0.5px -0.5px 0px 1.5px var(--WindowFrame), 0px 0px 0px 2px var(--WindowFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button svg path {
|
||||||
|
fill: var(--ButtonText);
|
||||||
|
}
|
||||||
|
|
||||||
|
.titlebar .button:active {
|
||||||
|
box-shadow: -0.5px -0.5px 0px 0.5px var(--ButtonShadow), 0px 0px 0px 1px var(--ButtonLight), -0.5px -0.5px 0px 1.5px var(--ButtonDkShadow), 0px 0px 0px 2px var(--ButtonHilight);
|
||||||
|
}
|
||||||
|
|
||||||
|
#status {
|
||||||
|
margin: 40px 32px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="window active">
|
||||||
|
<div class="titlebar">
|
||||||
|
<span class="titlebar-title">3D Pinball for Windows - Space Cadet</span>
|
||||||
|
<div class="titlebar-wincontrols">
|
||||||
|
<ul class="buttons-wrapper">
|
||||||
|
<li class="button minimize">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="0.125017in" height="0.104181in"
|
||||||
|
viewBox="0 0 12 10" class="button-content replaced-svg">
|
||||||
|
<path id="Minimize" fill="black" stroke="black" stroke-width="0"
|
||||||
|
d="M 2.00,7.00 C 2.00,7.00 8.00,7.00 8.00,7.00 8.00,7.00 8.00,9.00 8.00,9.00 8.00,9.00 2.00,9.00 2.00,9.00 2.00,9.00 2.00,7.00 2.00,7.00 Z">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
</li>
|
||||||
|
<li class="button maximize">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="0.125017in" height="0.104181in"
|
||||||
|
viewBox="0 0 12 10" class="button-content replaced-svg">
|
||||||
|
<path id="Maximize" fill="black" stroke="black" stroke-width="0"
|
||||||
|
d="M 2.00,2.00 C 2.00,2.00 9.00,2.00 9.00,2.00 9.00,2.00 9.00,8.00 9.00,8.00 9.00,8.00 2.00,8.00 2.00,8.00 2.00,8.00 2.00,2.00 2.00,2.00 Z M 1.00,0.00 C 1.00,0.00 1.00,9.00 1.00,9.00 1.00,9.00 10.00,9.00 10.00,9.00 10.00,9.00 10.00,0.00 10.00,0.00 10.00,0.00 1.00,0.00 1.00,0.00 Z">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
</li>
|
||||||
|
<li class="spacer"></li>
|
||||||
|
<li class="button close">
|
||||||
|
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1"
|
||||||
|
width="12" height="10" class="button-content replaced-svg">
|
||||||
|
<g transform="translate(0,-1042.3622)" id="layer1">
|
||||||
|
<path
|
||||||
|
d="m 2.0025485,1043.3853 2.0198115,0 0,1.0185 0.984011,0 0,0.984 1.985286,0 0,-0.984 1.001274,0 0,-1.0185 2.002548,0 0,1.0013 0,0 0,0 -1.001274,0 0,1.0012 -1.001274,0 0,0.9841 -1.001274,0 0,1.0012 1.001274,0 0,1.0013 1.001274,0 0,1.0013 1.001274,0 0,1.0013 -2.002548,0 0,-1.0013 -1.001274,0 0,-0.984 -1.985286,0 0,0.984 -1.001274,0 0,1.0013 -2.0025485,0 0,-1.0013 1.0012745,0 0,-1.0013 1.001274,0 0,-1.0013 1.001274,0 0,-1.0012 -1.001274,0 0,-0.9841 -1.001274,0 0,-1.0012 -1.0012745,0 0,-1.0186 z"
|
||||||
|
id="Close" fill="black" stroke-width="0"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="emscripten" id="status">Downloading...</div>
|
||||||
|
<div class="emscripten">
|
||||||
|
<progress value="0" max="100" id="progress" hidden=1></progress>
|
||||||
|
</div>
|
||||||
|
<canvas class="emscripten" id="canvas" style="display:none;" oncontextmenu="event.preventDefault()"
|
||||||
|
tabindex="-1"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--<textarea class="emscripten" id="output" rows="8"></textarea>-->
|
||||||
|
|
||||||
|
<script type='text/javascript'>
|
||||||
|
var statusElement = document.getElementById('status');
|
||||||
|
var progressElement = document.getElementById('progress');
|
||||||
|
|
||||||
|
var Module = {
|
||||||
|
preRun: [],
|
||||||
|
postRun: [],
|
||||||
|
print: (function () {
|
||||||
|
var element = document.getElementById('output');
|
||||||
|
if (element) element.value = ''; // clear browser cache
|
||||||
|
return function (text) {
|
||||||
|
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
|
||||||
|
// These replacements are necessary if you render to raw HTML
|
||||||
|
//text = text.replace(/&/g, "&");
|
||||||
|
//text = text.replace(/</g, "<");
|
||||||
|
//text = text.replace(/>/g, ">");
|
||||||
|
//text = text.replace('\n', '<br>', 'g');
|
||||||
|
console.log(text);
|
||||||
|
/*if (element) {
|
||||||
|
element.value += text + "\n";
|
||||||
|
element.scrollTop = element.scrollHeight; // focus on bottom
|
||||||
|
}*/
|
||||||
|
};
|
||||||
|
})(),
|
||||||
|
printErr: function (text) {
|
||||||
|
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
|
||||||
|
console.error(text);
|
||||||
|
},
|
||||||
|
canvas: (function () {
|
||||||
|
var canvas = document.getElementById('canvas');
|
||||||
|
|
||||||
|
// As a default initial behavior, pop up an alert when webgl context is lost. To make your
|
||||||
|
// application robust, you may want to override this behavior before shipping!
|
||||||
|
// See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
|
||||||
|
canvas.addEventListener("webglcontextlost", function (e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false);
|
||||||
|
|
||||||
|
return canvas;
|
||||||
|
})(),
|
||||||
|
setStatus: function (text) {
|
||||||
|
if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' };
|
||||||
|
if (text === Module.setStatus.last.text) return;
|
||||||
|
|
||||||
|
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
|
||||||
|
var now = Date.now();
|
||||||
|
if (m && now - Module.setStatus.last.time < 30) return; // if this is a progress update, skip it if too soon
|
||||||
|
Module.setStatus.last.time = now;
|
||||||
|
Module.setStatus.last.text = text;
|
||||||
|
if (m) {
|
||||||
|
text = m[1];
|
||||||
|
progressElement.value = parseInt(m[2]) * 100;
|
||||||
|
progressElement.max = parseInt(m[4]) * 100;
|
||||||
|
progressElement.hidden = false;
|
||||||
|
} else {
|
||||||
|
progressElement.value = null;
|
||||||
|
progressElement.max = null;
|
||||||
|
progressElement.hidden = true;
|
||||||
|
|
||||||
|
var canvas = document.getElementById('canvas');
|
||||||
|
canvas.style.display = "";
|
||||||
|
}
|
||||||
|
statusElement.innerHTML = text;
|
||||||
|
|
||||||
|
if (text === "") {
|
||||||
|
statusElement.style.display = "none";
|
||||||
|
progressElement.style.display = "none";
|
||||||
|
} else {
|
||||||
|
statusElement.style.display = "";
|
||||||
|
progressElement.style.display = "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
totalDependencies: 0,
|
||||||
|
monitorRunDependencies: function (left) {
|
||||||
|
this.totalDependencies = Math.max(this.totalDependencies, left);
|
||||||
|
Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies - left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Module.setStatus('Downloading...');
|
||||||
|
window.onerror = function () {
|
||||||
|
Module.setStatus('Exception thrown, see JavaScript console');
|
||||||
|
Module.setStatus = function (text) {
|
||||||
|
if (text) Module.printErr('[post-exception status] ' + text);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
{{{ SCRIPT }}}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -39,6 +39,7 @@ int fullscrn::set_screen_mode(int isFullscreen)
|
||||||
int result = isFullscreen;
|
int result = isFullscreen;
|
||||||
if (isFullscreen == screen_mode)
|
if (isFullscreen == screen_mode)
|
||||||
return result;
|
return result;
|
||||||
|
#ifndef __EMSCRIPTEN__
|
||||||
screen_mode = isFullscreen;
|
screen_mode = isFullscreen;
|
||||||
if (isFullscreen)
|
if (isFullscreen)
|
||||||
{
|
{
|
||||||
|
@ -50,6 +51,7 @@ int fullscrn::set_screen_mode(int isFullscreen)
|
||||||
disableFullscreen();
|
disableFullscreen();
|
||||||
result = 1;
|
result = 1;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -5,7 +5,63 @@
|
||||||
#include "pb.h"
|
#include "pb.h"
|
||||||
#include "pinball.h"
|
#include "pinball.h"
|
||||||
|
|
||||||
Mix_Music* midi::currentMidi;
|
#ifndef TSF_RENDER_EFFECTSAMPLEBLOCK
|
||||||
|
#define TSF_RENDER_EFFECTSAMPLEBLOCK 64
|
||||||
|
#endif
|
||||||
|
|
||||||
|
midi_song midi::currentMidi = {false};
|
||||||
|
tml_message* midi::currentMessage = nullptr;
|
||||||
|
static float midiTime = 0.0f;
|
||||||
|
static float sampPerSec = 1000.0 / 22050.0;
|
||||||
|
static tsf* tsfSynth = nullptr;
|
||||||
|
|
||||||
|
void midi::sdl_audio_callback(void* data, Uint8 *stream, int len)
|
||||||
|
{
|
||||||
|
memset(stream, 0, len);
|
||||||
|
|
||||||
|
if (tsfSynth == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SampleBlock, SampleCount = (len / (2 * sizeof(short)));
|
||||||
|
for (SampleBlock = TSF_RENDER_EFFECTSAMPLEBLOCK; SampleCount; SampleCount -= SampleBlock, stream += (SampleBlock * (2 * sizeof(short))))
|
||||||
|
{
|
||||||
|
if (SampleBlock > SampleCount) SampleBlock = SampleCount;
|
||||||
|
|
||||||
|
for (midiTime += SampleBlock * sampPerSec; midi::currentMessage && midiTime >= midi::currentMessage->time; )
|
||||||
|
{
|
||||||
|
switch (midi::currentMessage->type)
|
||||||
|
{
|
||||||
|
case TML_PROGRAM_CHANGE:
|
||||||
|
tsf_channel_set_presetnumber(tsfSynth, midi::currentMessage->channel, midi::currentMessage->program, (midi::currentMessage->channel == 9));
|
||||||
|
tsf_channel_midi_control(tsfSynth, midi::currentMessage->channel, TML_ALL_NOTES_OFF, 0);
|
||||||
|
break;
|
||||||
|
case TML_NOTE_ON:
|
||||||
|
tsf_channel_note_on(tsfSynth, midi::currentMessage->channel, midi::currentMessage->key, midi::currentMessage->velocity / 127.0f);
|
||||||
|
break;
|
||||||
|
case TML_NOTE_OFF:
|
||||||
|
tsf_channel_note_off(tsfSynth, midi::currentMessage->channel, midi::currentMessage->key);
|
||||||
|
break;
|
||||||
|
case TML_PITCH_BEND:
|
||||||
|
tsf_channel_set_pitchwheel(tsfSynth, midi::currentMessage->channel, midi::currentMessage->pitch_bend);
|
||||||
|
break;
|
||||||
|
case TML_CONTROL_CHANGE:
|
||||||
|
tsf_channel_midi_control(tsfSynth, midi::currentMessage->channel, midi::currentMessage->control, midi::currentMessage->control_value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (midi::currentMessage->next == nullptr) {
|
||||||
|
midiTime = 0.0f;
|
||||||
|
midi::currentMessage = midi::currentMidi.start;
|
||||||
|
} else {
|
||||||
|
midi::currentMessage = midi::currentMessage->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the block of audio samples in float format
|
||||||
|
tsf_render_short(tsfSynth, (short*)stream, SampleBlock, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
constexpr uint32_t FOURCC(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
|
constexpr uint32_t FOURCC(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
|
||||||
{
|
{
|
||||||
|
@ -31,15 +87,29 @@ int midi::play_pb_theme(int flag)
|
||||||
{
|
{
|
||||||
if (pb::FullTiltMode)
|
if (pb::FullTiltMode)
|
||||||
{
|
{
|
||||||
return play_ft(track1);
|
return play_ft(&track1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef MUSIC_SDL
|
||||||
int result = 0;
|
int result = 0;
|
||||||
music_stop();
|
music_stop();
|
||||||
if (currentMidi)
|
if (currentMidi)
|
||||||
result = Mix_PlayMusic(currentMidi, -1);
|
result = Mix_PlayMusic(currentMidi.handle, -1);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
#elif defined(MUSIC_TSF)
|
||||||
|
int result = 0;
|
||||||
|
if (currentMidi.valid) {
|
||||||
|
currentMessage = currentMidi.start;
|
||||||
|
midiTime = 0.0f;
|
||||||
|
// Mix_HookMusic(midi::sdl_audio_callback, nullptr);
|
||||||
|
result = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
int midi::music_stop()
|
int midi::music_stop()
|
||||||
|
@ -49,9 +119,18 @@ int midi::music_stop()
|
||||||
return stop_ft();
|
return stop_ft();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef MUSIC_SDL
|
||||||
return Mix_HaltMusic();
|
return Mix_HaltMusic();
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef MUSIC_TSF
|
||||||
|
extern unsigned char gm_sf2[];
|
||||||
|
extern unsigned int gm_sf2_len;
|
||||||
|
#endif
|
||||||
|
|
||||||
int midi::music_init()
|
int midi::music_init()
|
||||||
{
|
{
|
||||||
if (pb::FullTiltMode)
|
if (pb::FullTiltMode)
|
||||||
|
@ -59,8 +138,36 @@ int midi::music_init()
|
||||||
return music_init_ft();
|
return music_init_ft();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef MUSIC_SDL
|
||||||
currentMidi = Mix_LoadMUS(pinball::get_rc_string(156, 0));
|
currentMidi = Mix_LoadMUS(pinball::get_rc_string(156, 0));
|
||||||
return currentMidi != nullptr;
|
return currentMidi != nullptr;
|
||||||
|
#elif defined(MUSIC_TSF)
|
||||||
|
currentMessage = nullptr;
|
||||||
|
currentMidi = {false};
|
||||||
|
|
||||||
|
|
||||||
|
tsfSynth = tsf_load_memory(gm_sf2, (int)gm_sf2_len);
|
||||||
|
|
||||||
|
int sampleRate;
|
||||||
|
if (Mix_QuerySpec(&sampleRate, nullptr, nullptr)) {
|
||||||
|
tsf_set_output(tsfSynth, TSF_STEREO_INTERLEAVED, sampleRate, 0.0f);
|
||||||
|
sampPerSec = 1000.0f / float(sampleRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto fileName = std::string(pinball::get_rc_string(156, 0));
|
||||||
|
std::transform(fileName.begin(), fileName.end(), fileName.begin(), [](unsigned char c) { return std::toupper(c); });
|
||||||
|
auto filePath = pinball::make_path_name(fileName);
|
||||||
|
|
||||||
|
auto midi = tml_load_filename(filePath.c_str());
|
||||||
|
if (midi != nullptr) {
|
||||||
|
currentMidi = {true, midi};
|
||||||
|
}
|
||||||
|
|
||||||
|
Mix_HookMusic(midi::sdl_audio_callback, nullptr);
|
||||||
|
return currentMidi.valid;
|
||||||
|
#else
|
||||||
|
return 1;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void midi::music_shutdown()
|
void midi::music_shutdown()
|
||||||
|
@ -71,44 +178,53 @@ void midi::music_shutdown()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Mix_FreeMusic(currentMidi);
|
#ifdef MUSIC_SDL
|
||||||
|
Mix_FreeMusic(currentMidi.handle);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
objlist_class<Mix_Music>* midi::TrackList;
|
std::vector<midi_song> midi::TrackList;
|
||||||
Mix_Music *midi::track1, *midi::track2, *midi::track3, *midi::active_track, *midi::active_track2;
|
midi_song midi::track1, midi::track2, midi::track3, midi::active_track, midi::active_track2;
|
||||||
int midi::some_flag1;
|
int midi::some_flag1;
|
||||||
|
|
||||||
int midi::music_init_ft()
|
int midi::music_init_ft()
|
||||||
{
|
{
|
||||||
active_track = nullptr;
|
active_track = {false};
|
||||||
TrackList = new objlist_class<Mix_Music>(0, 1);
|
//TrackList = new objlist_class<midi_song>(0, 1);
|
||||||
|
TrackList.clear();
|
||||||
|
|
||||||
track1 = load_track("taba1");
|
track1 = load_track("taba1");
|
||||||
track2 = load_track("taba2");
|
track2 = load_track("taba2");
|
||||||
track3 = load_track("taba3");
|
track3 = load_track("taba3");
|
||||||
if (!track2)
|
if (!track2.valid)
|
||||||
track2 = track1;
|
track2 = track1;
|
||||||
if (!track3)
|
if (!track3.valid)
|
||||||
track3 = track1;
|
track3 = track1;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void midi::music_shutdown_ft()
|
void midi::music_shutdown_ft()
|
||||||
{
|
{
|
||||||
if (active_track)
|
#ifdef MUSIC_SDL
|
||||||
|
if (active_track.valid)
|
||||||
Mix_HaltMusic();
|
Mix_HaltMusic();
|
||||||
while (TrackList->GetCount())
|
/*while (TrackList->GetCount())
|
||||||
{
|
{
|
||||||
auto midi = TrackList->Get(0);
|
auto midi = TrackList->Get(0);
|
||||||
Mix_FreeMusic(midi);
|
Mix_FreeMusic(midi.handle);
|
||||||
TrackList->Delete(midi);
|
TrackList->Delete(midi);
|
||||||
}
|
}*/
|
||||||
active_track = nullptr;
|
|
||||||
|
active_track = {false};
|
||||||
delete TrackList;
|
delete TrackList;
|
||||||
|
#elif defined(MUSIC_TSF)
|
||||||
|
|
||||||
|
active_track = {false};
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
Mix_Music* midi::load_track(std::string fileName)
|
midi_song midi::load_track(std::string fileName)
|
||||||
{
|
{
|
||||||
auto origFile = fileName;
|
auto origFile = fileName;
|
||||||
|
|
||||||
|
@ -125,7 +241,7 @@ Mix_Music* midi::load_track(std::string fileName)
|
||||||
auto filePath = pinball::make_path_name(fileName);
|
auto filePath = pinball::make_path_name(fileName);
|
||||||
auto midi = MdsToMidi(filePath);
|
auto midi = MdsToMidi(filePath);
|
||||||
if (!midi)
|
if (!midi)
|
||||||
return nullptr;
|
return {false};
|
||||||
|
|
||||||
// Dump converted MIDI file
|
// Dump converted MIDI file
|
||||||
/*origFile += ".midi";
|
/*origFile += ".midi";
|
||||||
|
@ -133,29 +249,41 @@ Mix_Music* midi::load_track(std::string fileName)
|
||||||
fwrite(midi->data(), 1, midi->size(), fileHandle);
|
fwrite(midi->data(), 1, midi->size(), fileHandle);
|
||||||
fclose(fileHandle);*/
|
fclose(fileHandle);*/
|
||||||
|
|
||||||
|
#ifdef MUSIC_SDL
|
||||||
auto rw = SDL_RWFromMem(midi->data(), static_cast<int>(midi->size()));
|
auto rw = SDL_RWFromMem(midi->data(), static_cast<int>(midi->size()));
|
||||||
auto audio = Mix_LoadMUS_RW(rw, 1); // This call seems to leak memory no matter what.
|
auto audio = Mix_LoadMUS_RW(rw, 1); // This call seems to leak memory no matter what.
|
||||||
|
|
||||||
|
#elif defined(MUSIC_TSF)
|
||||||
|
auto audio = tml_load_memory(midi->data(), static_cast<int>(midi->size()));
|
||||||
|
#else
|
||||||
|
void* audio = nullptr;
|
||||||
|
#endif
|
||||||
delete midi;
|
delete midi;
|
||||||
if (!audio)
|
if (!audio)
|
||||||
return nullptr;
|
return {false};
|
||||||
|
|
||||||
TrackList->Add(audio);
|
midi_song song = {true, audio};
|
||||||
return audio;
|
TrackList.push_back(song);
|
||||||
|
|
||||||
|
return song;
|
||||||
}
|
}
|
||||||
|
|
||||||
int midi::play_ft(Mix_Music* midi)
|
int midi::play_ft(midi_song* midi)
|
||||||
{
|
{
|
||||||
int result;
|
int result = 0;
|
||||||
|
|
||||||
stop_ft();
|
stop_ft();
|
||||||
if (!midi)
|
if (!midi || !midi->valid)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (some_flag1)
|
if (some_flag1)
|
||||||
{
|
{
|
||||||
active_track2 = midi;
|
active_track2 = *midi;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (Mix_PlayMusic(midi, -1))
|
|
||||||
|
#ifdef MUSIC_SDL
|
||||||
|
if (Mix_PlayMusic(midi.handle, -1))
|
||||||
{
|
{
|
||||||
active_track = nullptr;
|
active_track = nullptr;
|
||||||
result = 0;
|
result = 0;
|
||||||
|
@ -165,15 +293,32 @@ int midi::play_ft(Mix_Music* midi)
|
||||||
active_track = midi;
|
active_track = midi;
|
||||||
result = 1;
|
result = 1;
|
||||||
}
|
}
|
||||||
|
#elif defined(MUSIC_TSF)
|
||||||
|
active_track = *midi;
|
||||||
|
result = 1;
|
||||||
|
#endif
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
int midi::stop_ft()
|
int midi::stop_ft()
|
||||||
{
|
{
|
||||||
int returnCode = 0;
|
int returnCode = 0;
|
||||||
if (active_track)
|
|
||||||
|
#ifdef MUSIC_SDL
|
||||||
|
if (active_track.valid)
|
||||||
returnCode = Mix_HaltMusic();
|
returnCode = Mix_HaltMusic();
|
||||||
active_track = nullptr;
|
|
||||||
|
active_track.valid = false;
|
||||||
|
active_track.handle = nullptr;
|
||||||
|
#elif defined(MUSIC_TSF)
|
||||||
|
// Mix_HookMusic(nullptr, nullptr);
|
||||||
|
tsf_note_off_all(tsfSynth);
|
||||||
|
active_track = {false, nullptr};
|
||||||
|
currentMessage = nullptr;
|
||||||
|
midiTime = 0.0f;
|
||||||
|
#endif
|
||||||
|
|
||||||
return returnCode;
|
return returnCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "objlist_class.h"
|
#include "objlist_class.h"
|
||||||
|
|
||||||
|
#ifdef MUSIC_TSF
|
||||||
|
#include "tml.h"
|
||||||
|
#include "tsf.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
constexpr uint32_t SwapByteOrderInt(uint32_t val)
|
constexpr uint32_t SwapByteOrderInt(uint32_t val)
|
||||||
{
|
{
|
||||||
return (val >> 24) |
|
return (val >> 24) |
|
||||||
|
@ -84,6 +89,18 @@ static_assert(sizeof(midi_track) == 8, "Wrong size of midi_track");
|
||||||
|
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
struct midi_song
|
||||||
|
{
|
||||||
|
bool valid;
|
||||||
|
#ifdef MUSIC_SDL
|
||||||
|
Mix_Music* handle;
|
||||||
|
#elif defined(MUSIC_TSF)
|
||||||
|
tml_message* start;
|
||||||
|
#else
|
||||||
|
void* dummy;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
class midi
|
class midi
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -92,15 +109,19 @@ public:
|
||||||
static int music_init();
|
static int music_init();
|
||||||
static void music_shutdown();
|
static void music_shutdown();
|
||||||
private:
|
private:
|
||||||
static Mix_Music* currentMidi;
|
static midi_song currentMidi;
|
||||||
|
#ifdef MUSIC_TSF
|
||||||
|
static tml_message* currentMessage;
|
||||||
|
static void sdl_audio_callback(void* data, Uint8 *stream, int len);
|
||||||
|
#endif
|
||||||
|
|
||||||
static objlist_class<Mix_Music>* TrackList;
|
static std::vector<midi_song> TrackList;
|
||||||
static Mix_Music *track1, *track2, *track3, *active_track, *active_track2;
|
static midi_song track1, track2, track3, active_track, active_track2;
|
||||||
static int some_flag1;
|
static int some_flag1;
|
||||||
static int music_init_ft();
|
static int music_init_ft();
|
||||||
static void music_shutdown_ft();
|
static void music_shutdown_ft();
|
||||||
static Mix_Music* load_track(std::string fileName);
|
static midi_song load_track(std::string fileName);
|
||||||
static int play_ft(Mix_Music* midi);
|
static int play_ft(midi_song* midi);
|
||||||
static int stop_ft();
|
static int stop_ft();
|
||||||
static std::vector<uint8_t>* MdsToMidi(std::string file);
|
static std::vector<uint8_t>* MdsToMidi(std::string file);
|
||||||
};
|
};
|
||||||
|
|
|
@ -64,7 +64,7 @@ void options::init()
|
||||||
}
|
}
|
||||||
|
|
||||||
Options.Sounds = 1;
|
Options.Sounds = 1;
|
||||||
Options.Music = 0;
|
Options.Music = 1;
|
||||||
Options.FullScreen = 0;
|
Options.FullScreen = 0;
|
||||||
Options.LeftFlipperKeyDft = SDLK_z;
|
Options.LeftFlipperKeyDft = SDLK_z;
|
||||||
Options.RightFlipperKeyDft = SDLK_SLASH;
|
Options.RightFlipperKeyDft = SDLK_SLASH;
|
||||||
|
|
|
@ -0,0 +1,518 @@
|
||||||
|
/* TinyMidiLoader - v0.7 - Minimalistic midi parsing library - https://github.com/schellingb/TinySoundFont
|
||||||
|
no warranty implied; use at your own risk
|
||||||
|
Do this:
|
||||||
|
#define TML_IMPLEMENTATION
|
||||||
|
before you include this file in *one* C or C++ file to create the implementation.
|
||||||
|
// i.e. it should look like this:
|
||||||
|
#include ...
|
||||||
|
#include ...
|
||||||
|
#define TML_IMPLEMENTATION
|
||||||
|
#include "tml.h"
|
||||||
|
|
||||||
|
[OPTIONAL] #define TML_NO_STDIO to remove stdio dependency
|
||||||
|
[OPTIONAL] #define TML_MALLOC, TML_REALLOC, and TML_FREE to avoid stdlib.h
|
||||||
|
[OPTIONAL] #define TML_MEMCPY to avoid string.h
|
||||||
|
|
||||||
|
LICENSE (ZLIB)
|
||||||
|
|
||||||
|
Copyright (C) 2017, 2018, 2020 Bernhard Schelling
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
|
including commercial applications, and to alter it and redistribute it
|
||||||
|
freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must not
|
||||||
|
claim that you wrote the original software. If you use this software
|
||||||
|
in a product, an acknowledgment in the product documentation would be
|
||||||
|
appreciated but is not required.
|
||||||
|
2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
misrepresented as being the original software.
|
||||||
|
3. This notice may not be removed or altered from any source distribution.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TML_INCLUDE_TML_INL
|
||||||
|
#define TML_INCLUDE_TML_INL
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Define this if you want the API functions to be static
|
||||||
|
#ifdef TML_STATIC
|
||||||
|
#define TMLDEF static
|
||||||
|
#else
|
||||||
|
#define TMLDEF extern
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Channel message type
|
||||||
|
enum TMLMessageType
|
||||||
|
{
|
||||||
|
TML_NOTE_OFF = 0x80, TML_NOTE_ON = 0x90, TML_KEY_PRESSURE = 0xA0, TML_CONTROL_CHANGE = 0xB0, TML_PROGRAM_CHANGE = 0xC0, TML_CHANNEL_PRESSURE = 0xD0, TML_PITCH_BEND = 0xE0, TML_SET_TEMPO = 0x51
|
||||||
|
};
|
||||||
|
|
||||||
|
// Midi controller numbers
|
||||||
|
enum TMLController
|
||||||
|
{
|
||||||
|
TML_BANK_SELECT_MSB, TML_MODULATIONWHEEL_MSB, TML_BREATH_MSB, TML_FOOT_MSB = 4, TML_PORTAMENTO_TIME_MSB, TML_DATA_ENTRY_MSB, TML_VOLUME_MSB,
|
||||||
|
TML_BALANCE_MSB, TML_PAN_MSB = 10, TML_EXPRESSION_MSB, TML_EFFECTS1_MSB, TML_EFFECTS2_MSB, TML_GPC1_MSB = 16, TML_GPC2_MSB, TML_GPC3_MSB, TML_GPC4_MSB,
|
||||||
|
TML_BANK_SELECT_LSB = 32, TML_MODULATIONWHEEL_LSB, TML_BREATH_LSB, TML_FOOT_LSB = 36, TML_PORTAMENTO_TIME_LSB, TML_DATA_ENTRY_LSB, TML_VOLUME_LSB,
|
||||||
|
TML_BALANCE_LSB, TML_PAN_LSB = 42, TML_EXPRESSION_LSB, TML_EFFECTS1_LSB, TML_EFFECTS2_LSB, TML_GPC1_LSB = 48, TML_GPC2_LSB, TML_GPC3_LSB, TML_GPC4_LSB,
|
||||||
|
TML_SUSTAIN_SWITCH = 64, TML_PORTAMENTO_SWITCH, TML_SOSTENUTO_SWITCH, TML_SOFT_PEDAL_SWITCH, TML_LEGATO_SWITCH, TML_HOLD2_SWITCH,
|
||||||
|
TML_SOUND_CTRL1, TML_SOUND_CTRL2, TML_SOUND_CTRL3, TML_SOUND_CTRL4, TML_SOUND_CTRL5, TML_SOUND_CTRL6,
|
||||||
|
TML_SOUND_CTRL7, TML_SOUND_CTRL8, TML_SOUND_CTRL9, TML_SOUND_CTRL10, TML_GPC5, TML_GPC6, TML_GPC7, TML_GPC8,
|
||||||
|
TML_PORTAMENTO_CTRL, TML_FX_REVERB = 91, TML_FX_TREMOLO, TML_FX_CHORUS, TML_FX_CELESTE_DETUNE, TML_FX_PHASER,
|
||||||
|
TML_DATA_ENTRY_INCR, TML_DATA_ENTRY_DECR, TML_NRPN_LSB, TML_NRPN_MSB, TML_RPN_LSB, TML_RPN_MSB,
|
||||||
|
TML_ALL_SOUND_OFF = 120, TML_ALL_CTRL_OFF, TML_LOCAL_CONTROL, TML_ALL_NOTES_OFF, TML_OMNI_OFF, TML_OMNI_ON, TML_POLY_OFF, TML_POLY_ON
|
||||||
|
};
|
||||||
|
|
||||||
|
// A single MIDI message linked to the next message in time
|
||||||
|
typedef struct tml_message
|
||||||
|
{
|
||||||
|
// Time of the message in milliseconds
|
||||||
|
unsigned int time;
|
||||||
|
|
||||||
|
// Type (see TMLMessageType) and channel number
|
||||||
|
unsigned char type, channel;
|
||||||
|
|
||||||
|
// 2 byte of parameter data based on the type:
|
||||||
|
// - key, velocity for TML_NOTE_ON and TML_NOTE_OFF messages
|
||||||
|
// - key, key_pressure for TML_KEY_PRESSURE messages
|
||||||
|
// - control, control_value for TML_CONTROL_CHANGE messages (see TMLController)
|
||||||
|
// - program for TML_PROGRAM_CHANGE messages
|
||||||
|
// - channel_pressure for TML_CHANNEL_PRESSURE messages
|
||||||
|
// - pitch_bend for TML_PITCH_BEND messages
|
||||||
|
union
|
||||||
|
{
|
||||||
|
struct { union { char key, control, program, channel_pressure; }; union { char velocity, key_pressure, control_value; }; };
|
||||||
|
struct { unsigned short pitch_bend; };
|
||||||
|
};
|
||||||
|
|
||||||
|
// The pointer to the next message in time following this event
|
||||||
|
struct tml_message* next;
|
||||||
|
} tml_message;
|
||||||
|
|
||||||
|
// The load functions will return a pointer to a struct tml_message.
|
||||||
|
// Normally the linked list gets traversed by following the next pointers.
|
||||||
|
// Make sure to keep the pointer to the first message to free the memory.
|
||||||
|
// On error the tml_load* functions will return NULL most likely due to an
|
||||||
|
// invalid MIDI stream (or if the file did not exist in tml_load_filename).
|
||||||
|
|
||||||
|
#ifndef TML_NO_STDIO
|
||||||
|
// Directly load a MIDI file from a .mid file path
|
||||||
|
TMLDEF tml_message* tml_load_filename(const char* filename);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Load a MIDI file from a block of memory
|
||||||
|
TMLDEF tml_message* tml_load_memory(const void* buffer, int size);
|
||||||
|
|
||||||
|
// Get infos about this loaded MIDI file, returns the note count
|
||||||
|
// NULL can be passed for any output value pointer if not needed.
|
||||||
|
// used_channels: Will be set to how many channels play notes
|
||||||
|
// (i.e. 1 if channel 15 is used but no other)
|
||||||
|
// used_programs: Will be set to how many different programs are used
|
||||||
|
// total_notes: Will be set to the total number of note on messages
|
||||||
|
// time_first_note: Will be set to the time of the first note on message
|
||||||
|
// time_length: Will be set to the total time in milliseconds
|
||||||
|
TMLDEF int tml_get_info(tml_message* first_message, int* used_channels, int* used_programs, int* total_notes, unsigned int* time_first_note, unsigned int* time_length);
|
||||||
|
|
||||||
|
// Read the tempo (microseconds per quarter note) value from a message with the type TML_SET_TEMPO
|
||||||
|
TMLDEF int tml_get_tempo_value(tml_message* set_tempo_message);
|
||||||
|
|
||||||
|
// Free all the memory of the linked message list (can also call free() manually)
|
||||||
|
TMLDEF void tml_free(tml_message* f);
|
||||||
|
|
||||||
|
// Stream structure for the generic loading
|
||||||
|
struct tml_stream
|
||||||
|
{
|
||||||
|
// Custom data given to the functions as the first parameter
|
||||||
|
void* data;
|
||||||
|
|
||||||
|
// Function pointer will be called to read 'size' bytes into ptr (returns number of read bytes)
|
||||||
|
int (*read)(void* data, void* ptr, unsigned int size);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generic Midi loading method using the stream structure above
|
||||||
|
TMLDEF tml_message* tml_load(struct tml_stream* stream);
|
||||||
|
|
||||||
|
// If this library is used together with TinySoundFont, tsf_stream (equivalent to tml_stream) can also be used
|
||||||
|
struct tsf_stream;
|
||||||
|
TMLDEF tml_message* tml_load_tsf_stream(struct tsf_stream* stream);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// end header
|
||||||
|
// ---------------------------------------------------------------------------------------------------------
|
||||||
|
#endif //TML_INCLUDE_TML_INL
|
||||||
|
|
||||||
|
#ifdef TML_IMPLEMENTATION
|
||||||
|
|
||||||
|
#if !defined(TML_MALLOC) || !defined(TML_FREE) || !defined(TML_REALLOC)
|
||||||
|
# include <stdlib.h>
|
||||||
|
# define TML_MALLOC malloc
|
||||||
|
# define TML_FREE free
|
||||||
|
# define TML_REALLOC realloc
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(TML_MEMCPY)
|
||||||
|
# include <string.h>
|
||||||
|
# define TML_MEMCPY memcpy
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef TML_NO_STDIO
|
||||||
|
# include <stdio.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define TML_NULL 0
|
||||||
|
|
||||||
|
////crash on errors and warnings to find broken midi files while debugging
|
||||||
|
//#define TML_ERROR(msg) *(int*)0 = 0xbad;
|
||||||
|
//#define TML_WARN(msg) *(int*)0 = 0xf00d;
|
||||||
|
|
||||||
|
////print errors and warnings
|
||||||
|
//#define TML_ERROR(msg) printf("ERROR: %s\n", msg);
|
||||||
|
//#define TML_WARN(msg) printf("WARNING: %s\n", msg);
|
||||||
|
|
||||||
|
#ifndef TML_ERROR
|
||||||
|
#define TML_ERROR(msg)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef TML_WARN
|
||||||
|
#define TML_WARN(msg)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef TML_NO_STDIO
|
||||||
|
static int tml_stream_stdio_read(FILE* f, void* ptr, unsigned int size) { return (int)fread(ptr, 1, size, f); }
|
||||||
|
TMLDEF tml_message* tml_load_filename(const char* filename)
|
||||||
|
{
|
||||||
|
struct tml_message* res;
|
||||||
|
struct tml_stream stream = { TML_NULL, (int(*)(void*,void*,unsigned int))&tml_stream_stdio_read };
|
||||||
|
#if __STDC_WANT_SECURE_LIB__
|
||||||
|
FILE* f = TML_NULL; fopen_s(&f, filename, "rb");
|
||||||
|
#else
|
||||||
|
FILE* f = fopen(filename, "rb");
|
||||||
|
#endif
|
||||||
|
if (!f) { TML_ERROR("File not found"); return 0; }
|
||||||
|
stream.data = f;
|
||||||
|
res = tml_load(&stream);
|
||||||
|
fclose(f);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct tml_stream_memory { const char* buffer; unsigned int total, pos; };
|
||||||
|
static int tml_stream_memory_read(struct tml_stream_memory* m, void* ptr, unsigned int size) { if (size > m->total - m->pos) size = m->total - m->pos; TML_MEMCPY(ptr, m->buffer+m->pos, size); m->pos += size; return size; }
|
||||||
|
TMLDEF struct tml_message* tml_load_memory(const void* buffer, int size)
|
||||||
|
{
|
||||||
|
struct tml_stream stream = { TML_NULL, (int(*)(void*,void*,unsigned int))&tml_stream_memory_read };
|
||||||
|
struct tml_stream_memory f = { 0, 0, 0 };
|
||||||
|
f.buffer = (const char*)buffer;
|
||||||
|
f.total = size;
|
||||||
|
stream.data = &f;
|
||||||
|
return tml_load(&stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct tml_track
|
||||||
|
{
|
||||||
|
unsigned int Idx, End, Ticks;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct tml_tempomsg
|
||||||
|
{
|
||||||
|
unsigned int time;
|
||||||
|
unsigned char type, Tempo[3];
|
||||||
|
tml_message* next;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct tml_parser
|
||||||
|
{
|
||||||
|
unsigned char *buf, *buf_end;
|
||||||
|
int last_status, message_array_size, message_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum TMLSystemType
|
||||||
|
{
|
||||||
|
TML_TEXT = 0x01, TML_COPYRIGHT = 0x02, TML_TRACK_NAME = 0x03, TML_INST_NAME = 0x04, TML_LYRIC = 0x05, TML_MARKER = 0x06, TML_CUE_POINT = 0x07,
|
||||||
|
TML_EOT = 0x2f, TML_SMPTE_OFFSET = 0x54, TML_TIME_SIGNATURE = 0x58, TML_KEY_SIGNATURE = 0x59, TML_SEQUENCER_EVENT = 0x7f,
|
||||||
|
TML_SYSEX = 0xf0, TML_TIME_CODE = 0xf1, TML_SONG_POSITION = 0xf2, TML_SONG_SELECT = 0xf3, TML_TUNE_REQUEST = 0xf6, TML_EOX = 0xf7, TML_SYNC = 0xf8,
|
||||||
|
TML_TICK = 0xf9, TML_START = 0xfa, TML_CONTINUE = 0xfb, TML_STOP = 0xfc, TML_ACTIVE_SENSING = 0xfe, TML_SYSTEM_RESET = 0xff
|
||||||
|
};
|
||||||
|
|
||||||
|
static int tml_readbyte(struct tml_parser* p)
|
||||||
|
{
|
||||||
|
return (p->buf == p->buf_end ? -1 : *(p->buf++));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int tml_readvariablelength(struct tml_parser* p)
|
||||||
|
{
|
||||||
|
unsigned int res = 0, i = 0;
|
||||||
|
unsigned char c;
|
||||||
|
for (; i != 4; i++)
|
||||||
|
{
|
||||||
|
if (p->buf == p->buf_end) { TML_WARN("Unexpected end of file"); return -1; }
|
||||||
|
c = *(p->buf++);
|
||||||
|
if (c & 0x80) res = ((res | (c & 0x7F)) << 7);
|
||||||
|
else return (int)(res | c);
|
||||||
|
}
|
||||||
|
TML_WARN("Invalid variable length byte count"); return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int tml_parsemessage(tml_message** f, struct tml_parser* p)
|
||||||
|
{
|
||||||
|
int deltatime = tml_readvariablelength(p), status = tml_readbyte(p);
|
||||||
|
tml_message* evt;
|
||||||
|
|
||||||
|
if (deltatime & 0xFFF00000) deltatime = 0; //throw away delays that are insanely high for malformatted midis
|
||||||
|
if (status < 0) { TML_WARN("Unexpected end of file"); return -1; }
|
||||||
|
if ((status & 0x80) == 0)
|
||||||
|
{
|
||||||
|
// Invalid, use same status as before
|
||||||
|
if ((p->last_status & 0x80) == 0) { TML_WARN("Undefined status and invalid running status"); return -1; }
|
||||||
|
p->buf--;
|
||||||
|
status = p->last_status;
|
||||||
|
}
|
||||||
|
else p->last_status = status;
|
||||||
|
|
||||||
|
if (p->message_array_size == p->message_count)
|
||||||
|
{
|
||||||
|
//start allocated memory size of message array at 64, double each time until 8192, then add 1024 entries until done
|
||||||
|
p->message_array_size += (!p->message_array_size ? 64 : (p->message_array_size > 4096 ? 1024 : p->message_array_size));
|
||||||
|
*f = (tml_message*)TML_REALLOC(*f, p->message_array_size * sizeof(tml_message));
|
||||||
|
if (!*f) { TML_ERROR("Out of memory"); return -1; }
|
||||||
|
}
|
||||||
|
evt = *f + p->message_count;
|
||||||
|
|
||||||
|
//check what message we have
|
||||||
|
if ((status == TML_SYSEX) || (status == TML_EOX)) //sysex
|
||||||
|
{
|
||||||
|
//sysex messages are not handled
|
||||||
|
p->buf += tml_readvariablelength(p);
|
||||||
|
if (p->buf > p->buf_end) { TML_WARN("Unexpected end of file"); p->buf = p->buf_end; return -1; }
|
||||||
|
evt->type = 0;
|
||||||
|
}
|
||||||
|
else if (status == 0xFF) //meta events
|
||||||
|
{
|
||||||
|
int meta_type = tml_readbyte(p), buflen = tml_readvariablelength(p);
|
||||||
|
unsigned char* metadata = p->buf;
|
||||||
|
if (meta_type < 0) { TML_WARN("Unexpected end of file"); return -1; }
|
||||||
|
if (buflen > 0 && (p->buf += buflen) > p->buf_end) { TML_WARN("Unexpected end of file"); p->buf = p->buf_end; return -1; }
|
||||||
|
|
||||||
|
switch (meta_type)
|
||||||
|
{
|
||||||
|
case TML_EOT:
|
||||||
|
if (buflen != 0) { TML_WARN("Invalid length for EndOfTrack event"); return -1; }
|
||||||
|
if (!deltatime) return TML_EOT; //no need to store this message
|
||||||
|
evt->type = TML_EOT;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TML_SET_TEMPO:
|
||||||
|
if (buflen != 3) { TML_WARN("Invalid length for SetTempo meta event"); return -1; }
|
||||||
|
evt->type = TML_SET_TEMPO;
|
||||||
|
((struct tml_tempomsg*)evt)->Tempo[0] = metadata[0];
|
||||||
|
((struct tml_tempomsg*)evt)->Tempo[1] = metadata[1];
|
||||||
|
((struct tml_tempomsg*)evt)->Tempo[2] = metadata[2];
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
evt->type = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else //channel message
|
||||||
|
{
|
||||||
|
int param;
|
||||||
|
if ((param = tml_readbyte(p)) < 0) { TML_WARN("Unexpected end of file"); return -1; }
|
||||||
|
evt->key = (param & 0x7f);
|
||||||
|
evt->channel = (status & 0x0f);
|
||||||
|
switch (evt->type = (status & 0xf0))
|
||||||
|
{
|
||||||
|
case TML_NOTE_OFF:
|
||||||
|
case TML_NOTE_ON:
|
||||||
|
case TML_KEY_PRESSURE:
|
||||||
|
case TML_CONTROL_CHANGE:
|
||||||
|
if ((param = tml_readbyte(p)) < 0) { TML_WARN("Unexpected end of file"); return -1; }
|
||||||
|
evt->velocity = (param & 0x7f);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TML_PITCH_BEND:
|
||||||
|
if ((param = tml_readbyte(p)) < 0) { TML_WARN("Unexpected end of file"); return -1; }
|
||||||
|
evt->pitch_bend = ((param & 0x7f) << 7) | evt->key;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TML_PROGRAM_CHANGE:
|
||||||
|
case TML_CHANNEL_PRESSURE:
|
||||||
|
evt->velocity = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: //ignore system/manufacture messages
|
||||||
|
evt->type = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deltatime || evt->type)
|
||||||
|
{
|
||||||
|
evt->time = deltatime;
|
||||||
|
p->message_count++;
|
||||||
|
}
|
||||||
|
return evt->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
TMLDEF tml_message* tml_load(struct tml_stream* stream)
|
||||||
|
{
|
||||||
|
int num_tracks, division, trackbufsize = 0;
|
||||||
|
unsigned char midi_header[14], *trackbuf = TML_NULL;
|
||||||
|
struct tml_message* messages = TML_NULL;
|
||||||
|
struct tml_track *tracks, *t, *tracksEnd;
|
||||||
|
struct tml_parser p = { TML_NULL, TML_NULL, 0, 0, 0 };
|
||||||
|
|
||||||
|
// Parse MIDI header
|
||||||
|
if (stream->read(stream->data, midi_header, 14) != 14) { TML_ERROR("Unexpected end of file"); return messages; }
|
||||||
|
if (midi_header[0] != 'M' || midi_header[1] != 'T' || midi_header[2] != 'h' || midi_header[3] != 'd' ||
|
||||||
|
midi_header[7] != 6 || midi_header[9] > 2) { TML_ERROR("Doesn't look like a MIDI file: invalid MThd header"); return messages; }
|
||||||
|
if (midi_header[12] & 0x80) { TML_ERROR("File uses unsupported SMPTE timing"); return messages; }
|
||||||
|
num_tracks = (int)(midi_header[10] << 8) | midi_header[11];
|
||||||
|
division = (int)(midi_header[12] << 8) | midi_header[13]; //division is ticks per beat (quarter-note)
|
||||||
|
if (num_tracks <= 0 && division <= 0) { TML_ERROR("Doesn't look like a MIDI file: invalid track or division values"); return messages; }
|
||||||
|
|
||||||
|
// Allocate temporary tracks array for parsing
|
||||||
|
tracks = (struct tml_track*)TML_MALLOC(sizeof(struct tml_track) * num_tracks);
|
||||||
|
tracksEnd = &tracks[num_tracks];
|
||||||
|
for (t = tracks; t != tracksEnd; t++) t->Idx = t->End = t->Ticks = 0;
|
||||||
|
|
||||||
|
// Read all messages for all tracks
|
||||||
|
for (t = tracks; t != tracksEnd; t++)
|
||||||
|
{
|
||||||
|
unsigned char track_header[8];
|
||||||
|
int track_length;
|
||||||
|
if (stream->read(stream->data, track_header, 8) != 8) { TML_WARN("Unexpected end of file"); break; }
|
||||||
|
if (track_header[0] != 'M' || track_header[1] != 'T' || track_header[2] != 'r' || track_header[3] != 'k')
|
||||||
|
{ TML_WARN("Invalid MTrk header"); break; }
|
||||||
|
|
||||||
|
// Get size of track data and read into buffer (allocate bigger buffer if needed)
|
||||||
|
track_length = track_header[7] | (track_header[6] << 8) | (track_header[5] << 16) | (track_header[4] << 24);
|
||||||
|
if (track_length < 0) { TML_WARN("Invalid MTrk header"); break; }
|
||||||
|
if (track_length > 0x100000) { TML_WARN("Track length is suspiciously big"); break; }
|
||||||
|
if (trackbufsize < track_length) { TML_FREE(trackbuf); trackbuf = (unsigned char*)TML_MALLOC(trackbufsize = track_length); }
|
||||||
|
if (stream->read(stream->data, trackbuf, track_length) != track_length) { TML_WARN("Unexpected end of file"); break; }
|
||||||
|
|
||||||
|
t->Idx = p.message_count;
|
||||||
|
for (p.buf_end = (p.buf = trackbuf) + track_length; p.buf != p.buf_end;)
|
||||||
|
{
|
||||||
|
int type = tml_parsemessage(&messages, &p);
|
||||||
|
if (type == TML_EOT || type < 0) break; //file end or illegal data encountered
|
||||||
|
}
|
||||||
|
if (p.buf != p.buf_end) { TML_WARN( "Track length did not match data length"); }
|
||||||
|
t->End = p.message_count;
|
||||||
|
}
|
||||||
|
TML_FREE(trackbuf);
|
||||||
|
|
||||||
|
// Change message time signature from delta ticks to actual msec values and link messages ordered by time
|
||||||
|
if (p.message_count)
|
||||||
|
{
|
||||||
|
tml_message *PrevMessage = TML_NULL, *Msg, *MsgEnd, Swap;
|
||||||
|
unsigned int ticks = 0, tempo_ticks = 0; //tick counter and value at last tempo change
|
||||||
|
int step_smallest, msec, tempo_msec = 0; //msec value at last tempo change
|
||||||
|
double ticks2time = 500000 / (1000.0 * division); //milliseconds per tick
|
||||||
|
|
||||||
|
// Loop through all messages over all tracks ordered by time
|
||||||
|
for (step_smallest = 0; step_smallest != 0x7fffffff; ticks += step_smallest)
|
||||||
|
{
|
||||||
|
step_smallest = 0x7fffffff;
|
||||||
|
msec = tempo_msec + (int)((ticks - tempo_ticks) * ticks2time);
|
||||||
|
for (t = tracks; t != tracksEnd; t++)
|
||||||
|
{
|
||||||
|
if (t->Idx == t->End) continue;
|
||||||
|
for (Msg = &messages[t->Idx], MsgEnd = &messages[t->End]; Msg != MsgEnd && t->Ticks + Msg->time == ticks; Msg++, t->Idx++)
|
||||||
|
{
|
||||||
|
t->Ticks += Msg->time;
|
||||||
|
if (Msg->type == TML_SET_TEMPO)
|
||||||
|
{
|
||||||
|
unsigned char* Tempo = ((struct tml_tempomsg*)Msg)->Tempo;
|
||||||
|
ticks2time = ((Tempo[0]<<16)|(Tempo[1]<<8)|Tempo[2])/(1000.0 * division);
|
||||||
|
tempo_msec = msec;
|
||||||
|
tempo_ticks = ticks;
|
||||||
|
}
|
||||||
|
if (Msg->type)
|
||||||
|
{
|
||||||
|
Msg->time = msec;
|
||||||
|
if (PrevMessage) { PrevMessage->next = Msg; PrevMessage = Msg; }
|
||||||
|
else { Swap = *Msg; *Msg = *messages; *messages = Swap; PrevMessage = messages; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Msg != MsgEnd && t->Ticks + Msg->time > ticks)
|
||||||
|
{
|
||||||
|
int step = (int)(t->Ticks + Msg->time - ticks);
|
||||||
|
if (step < step_smallest) step_smallest = step;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (PrevMessage) PrevMessage->next = TML_NULL;
|
||||||
|
else p.message_count = 0;
|
||||||
|
}
|
||||||
|
TML_FREE(tracks);
|
||||||
|
|
||||||
|
if (p.message_count == 0)
|
||||||
|
{
|
||||||
|
TML_FREE(messages);
|
||||||
|
messages = TML_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
TMLDEF tml_message* tml_load_tsf_stream(struct tsf_stream* stream)
|
||||||
|
{
|
||||||
|
return tml_load((struct tml_stream*)stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
TMLDEF int tml_get_info(tml_message* Msg, int* out_used_channels, int* out_used_programs, int* out_total_notes, unsigned int* out_time_first_note, unsigned int* out_time_length)
|
||||||
|
{
|
||||||
|
int used_programs = 0, used_channels = 0, total_notes = 0;
|
||||||
|
unsigned int time_first_note = 0xffffffff, time_length = 0;
|
||||||
|
unsigned char channels[16] = { 0 }, programs[128] = { 0 };
|
||||||
|
for (;Msg; Msg = Msg->next)
|
||||||
|
{
|
||||||
|
time_length = Msg->time;
|
||||||
|
if (Msg->type == TML_PROGRAM_CHANGE && !programs[(int)Msg->program]) { programs[(int)Msg->program] = 1; used_programs++; }
|
||||||
|
if (Msg->type != TML_NOTE_ON) continue;
|
||||||
|
if (time_first_note == 0xffffffff) time_first_note = time_length;
|
||||||
|
if (!channels[Msg->channel]) { channels[Msg->channel] = 1; used_channels++; }
|
||||||
|
total_notes++;
|
||||||
|
}
|
||||||
|
if (time_first_note == 0xffffffff) time_first_note = 0;
|
||||||
|
if (out_used_channels ) *out_used_channels = used_channels;
|
||||||
|
if (out_used_programs ) *out_used_programs = used_programs;
|
||||||
|
if (out_total_notes ) *out_total_notes = total_notes;
|
||||||
|
if (out_time_first_note) *out_time_first_note = time_first_note;
|
||||||
|
if (out_time_length ) *out_time_length = time_length;
|
||||||
|
return total_notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
TMLDEF int tml_get_tempo_value(tml_message* msg)
|
||||||
|
{
|
||||||
|
unsigned char* Tempo;
|
||||||
|
if (!msg || msg->type != TML_SET_TEMPO) return 0;
|
||||||
|
Tempo = ((struct tml_tempomsg*)msg)->Tempo;
|
||||||
|
return ((Tempo[0]<<16)|(Tempo[1]<<8)|Tempo[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
TMLDEF void tml_free(tml_message* f)
|
||||||
|
{
|
||||||
|
TML_FREE(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif //TML_IMPLEMENTATION
|
|
@ -0,0 +1,4 @@
|
||||||
|
#define TSF_IMPLEMENTATION
|
||||||
|
#define TML_IMPLEMENTATION
|
||||||
|
#include "tsf.h"
|
||||||
|
#include "tml.h"
|
File diff suppressed because it is too large
Load Diff
|
@ -10,6 +10,13 @@
|
||||||
#include "render.h"
|
#include "render.h"
|
||||||
#include "Sound.h"
|
#include "Sound.h"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
#include <emscripten.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
const double TargetFps = 60, TargetFrameTime = 1000 / TargetFps;
|
const double TargetFps = 60, TargetFrameTime = 1000 / TargetFps;
|
||||||
|
|
||||||
SDL_Window *winmain::MainWindow = nullptr;
|
SDL_Window *winmain::MainWindow = nullptr;
|
||||||
|
@ -43,7 +50,6 @@ bool winmain::DemoActive = false;
|
||||||
char *winmain::BasePath;
|
char *winmain::BasePath;
|
||||||
std::string winmain::FpsDetails;
|
std::string winmain::FpsDetails;
|
||||||
|
|
||||||
|
|
||||||
uint32_t timeGetTimeAlt()
|
uint32_t timeGetTimeAlt()
|
||||||
{
|
{
|
||||||
auto now = std::chrono::high_resolution_clock::now();
|
auto now = std::chrono::high_resolution_clock::now();
|
||||||
|
@ -52,6 +58,29 @@ uint32_t timeGetTimeAlt()
|
||||||
return static_cast<uint32_t>(millis);
|
return static_cast<uint32_t>(millis);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool loop_stop = false;
|
||||||
|
|
||||||
|
void run_loop(std::function<void()> fn)
|
||||||
|
{
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
emscripten_set_main_loop_arg([](void *arg)
|
||||||
|
{
|
||||||
|
auto *fn_ptr = (std::function<void()> *)arg;
|
||||||
|
if (!loop_stop && fn_ptr != nullptr)
|
||||||
|
{
|
||||||
|
auto &fn = *fn_ptr;
|
||||||
|
fn();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(void *)&fn, 60, 1);
|
||||||
|
#else
|
||||||
|
while (!loop_stop)
|
||||||
|
{
|
||||||
|
fn();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
int winmain::WinMain(LPCSTR lpCmdLine)
|
int winmain::WinMain(LPCSTR lpCmdLine)
|
||||||
{
|
{
|
||||||
restart = false;
|
restart = false;
|
||||||
|
@ -61,12 +90,17 @@ int winmain::WinMain(LPCSTR lpCmdLine)
|
||||||
|
|
||||||
// SDL init
|
// SDL init
|
||||||
SDL_SetMainReady();
|
SDL_SetMainReady();
|
||||||
if (SDL_Init(SDL_INIT_EVERYTHING) < 0)
|
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER | SDL_INIT_EVENTS) < 0)
|
||||||
{
|
{
|
||||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Could not initialize SDL2", SDL_GetError(), nullptr);
|
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Could not initialize SDL2", SDL_GetError(), nullptr);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
#ifndef __EMSCRIPTEN__
|
||||||
BasePath = SDL_GetBasePath();
|
BasePath = SDL_GetBasePath();
|
||||||
|
#else
|
||||||
|
BasePath = strdup("/game_resources/");
|
||||||
|
chdir(BasePath);
|
||||||
|
#endif
|
||||||
|
|
||||||
pinball::quickFlag = strstr(lpCmdLine, "-quick") != nullptr;
|
pinball::quickFlag = strstr(lpCmdLine, "-quick") != nullptr;
|
||||||
DatFileName = options::get_string("Pinball Data", pinball::get_rc_string(168, 0));
|
DatFileName = options::get_string("Pinball Data", pinball::get_rc_string(168, 0));
|
||||||
|
@ -82,13 +116,11 @@ int winmain::WinMain(LPCSTR lpCmdLine)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SDL window
|
// SDL window
|
||||||
SDL_Window* window = SDL_CreateWindow
|
SDL_Window *window = SDL_CreateWindow(
|
||||||
(
|
|
||||||
pinball::get_rc_string(38, 0),
|
pinball::get_rc_string(38, 0),
|
||||||
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
|
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
|
||||||
800, 556,
|
800, 556,
|
||||||
SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE
|
SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE);
|
||||||
);
|
|
||||||
MainWindow = window;
|
MainWindow = window;
|
||||||
if (!window)
|
if (!window)
|
||||||
{
|
{
|
||||||
|
@ -96,12 +128,10 @@ int winmain::WinMain(LPCSTR lpCmdLine)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_Renderer* renderer = SDL_CreateRenderer
|
SDL_Renderer *renderer = SDL_CreateRenderer(
|
||||||
(
|
|
||||||
window,
|
window,
|
||||||
-1,
|
-1,
|
||||||
SDL_RENDERER_ACCELERATED
|
SDL_RENDERER_ACCELERATED);
|
||||||
);
|
|
||||||
Renderer = renderer;
|
Renderer = renderer;
|
||||||
if (!renderer)
|
if (!renderer)
|
||||||
{
|
{
|
||||||
|
@ -154,10 +184,14 @@ int winmain::WinMain(LPCSTR lpCmdLine)
|
||||||
pb::reset_table();
|
pb::reset_table();
|
||||||
pb::firsttime_setup();
|
pb::firsttime_setup();
|
||||||
|
|
||||||
|
#ifndef __EMSCRIPTEN__
|
||||||
if (strstr(lpCmdLine, "-fullscreen"))
|
if (strstr(lpCmdLine, "-fullscreen"))
|
||||||
{
|
{
|
||||||
options::Options.FullScreen = 1;
|
options::Options.FullScreen = 1;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
options::Options.FullScreen = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
SDL_ShowWindow(window);
|
SDL_ShowWindow(window);
|
||||||
fullscrn::set_screen_mode(options::Options.FullScreen);
|
fullscrn::set_screen_mode(options::Options.FullScreen);
|
||||||
|
@ -172,7 +206,8 @@ int winmain::WinMain(LPCSTR lpCmdLine)
|
||||||
|
|
||||||
double sdlTimerResMs = 1000.0 / static_cast<double>(SDL_GetPerformanceFrequency());
|
double sdlTimerResMs = 1000.0 / static_cast<double>(SDL_GetPerformanceFrequency());
|
||||||
auto frameStart = static_cast<double>(SDL_GetPerformanceCounter());
|
auto frameStart = static_cast<double>(SDL_GetPerformanceCounter());
|
||||||
while (true)
|
|
||||||
|
run_loop([&]
|
||||||
{
|
{
|
||||||
if (!updateCounter)
|
if (!updateCounter)
|
||||||
{
|
{
|
||||||
|
@ -225,7 +260,10 @@ int winmain::WinMain(LPCSTR lpCmdLine)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ProcessWindowMessages() || bQuit)
|
if (!ProcessWindowMessages() || bQuit)
|
||||||
break;
|
{
|
||||||
|
loop_stop = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (has_focus)
|
if (has_focus)
|
||||||
{
|
{
|
||||||
|
@ -300,7 +338,7 @@ int winmain::WinMain(LPCSTR lpCmdLine)
|
||||||
printf("SDL Error: %s\n", sdlError);
|
printf("SDL Error: %s\n", sdlError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
gdrv::destroy_bitmap(&gfr_display);
|
gdrv::destroy_bitmap(&gfr_display);
|
||||||
options::uninit();
|
options::uninit();
|
||||||
|
@ -355,20 +393,24 @@ void winmain::RenderUi()
|
||||||
end_pause();
|
end_pause();
|
||||||
pb::toggle_demo();
|
pb::toggle_demo();
|
||||||
}
|
}
|
||||||
|
#ifndef __EMSCRIPTEN__
|
||||||
if (ImGui::MenuItem("Exit"))
|
if (ImGui::MenuItem("Exit"))
|
||||||
{
|
{
|
||||||
SDL_Event event{SDL_QUIT};
|
SDL_Event event{SDL_QUIT};
|
||||||
SDL_PushEvent(&event);
|
SDL_PushEvent(&event);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
ImGui::EndMenu();
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::BeginMenu("Options"))
|
if (ImGui::BeginMenu("Options"))
|
||||||
{
|
{
|
||||||
|
#ifndef __EMSCRIPTEN__
|
||||||
if (ImGui::MenuItem("Full Screen", "F4", options::Options.FullScreen))
|
if (ImGui::MenuItem("Full Screen", "F4", options::Options.FullScreen))
|
||||||
{
|
{
|
||||||
options::toggle(Menu1::Full_Screen);
|
options::toggle(Menu1::Full_Screen);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
if (ImGui::BeginMenu("Select Players"))
|
if (ImGui::BeginMenu("Select Players"))
|
||||||
{
|
{
|
||||||
if (ImGui::MenuItem("1 Player", nullptr, options::Options.Players == 1))
|
if (ImGui::MenuItem("1 Player", nullptr, options::Options.Players == 1))
|
||||||
|
@ -455,7 +497,7 @@ void winmain::RenderUi()
|
||||||
{
|
{
|
||||||
ShowImGuiDemo ^= true;
|
ShowImGuiDemo ^= true;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
if (ImGui::MenuItem("Sprite Viewer", nullptr, ShowSpriteViewer))
|
if (ImGui::MenuItem("Sprite Viewer", nullptr, ShowSpriteViewer))
|
||||||
{
|
{
|
||||||
if (!ShowSpriteViewer && !single_step)
|
if (!ShowSpriteViewer && !single_step)
|
||||||
|
@ -463,6 +505,7 @@ void winmain::RenderUi()
|
||||||
ShowSpriteViewer ^= true;
|
ShowSpriteViewer ^= true;
|
||||||
}
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
#endif
|
||||||
|
|
||||||
if (ImGui::MenuItem("About Pinball"))
|
if (ImGui::MenuItem("About Pinball"))
|
||||||
{
|
{
|
||||||
|
@ -675,6 +718,7 @@ int winmain::event_handler(const SDL_Event* event)
|
||||||
int winmain::ProcessWindowMessages()
|
int winmain::ProcessWindowMessages()
|
||||||
{
|
{
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
|
#ifndef __EMSCRIPTEN__
|
||||||
if (has_focus && !single_step)
|
if (has_focus && !single_step)
|
||||||
{
|
{
|
||||||
while (SDL_PollEvent(&event))
|
while (SDL_PollEvent(&event))
|
||||||
|
@ -688,6 +732,15 @@ int winmain::ProcessWindowMessages()
|
||||||
|
|
||||||
SDL_WaitEvent(&event);
|
SDL_WaitEvent(&event);
|
||||||
return event_handler(&event);
|
return event_handler(&event);
|
||||||
|
#else
|
||||||
|
while (SDL_PollEvent(&event))
|
||||||
|
{
|
||||||
|
if (!event_handler(&event))
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void winmain::memalloc_failure()
|
void winmain::memalloc_failure()
|
||||||
|
@ -723,6 +776,7 @@ void winmain::a_dialog()
|
||||||
SDL_OpenURL("https://github.com/k4zmu2a/SpaceCadetPinball");
|
SDL_OpenURL("https://github.com/k4zmu2a/SpaceCadetPinball");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
ImGui::TextUnformatted("Emscripten port by alula");
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
if (ImGui::Button("Ok"))
|
if (ImGui::Button("Ok"))
|
||||||
|
|
Loading…
Reference in New Issue