mirror of https://github.com/OpenTTD/OpenTTD
Compare commits
4 Commits
c9563735bc
...
2a8303fbee
Author | SHA1 | Date |
---|---|---|
|
2a8303fbee | |
|
6d95cea73a | |
|
921d83c324 | |
|
7edc18c977 |
|
@ -144,6 +144,7 @@ if(NOT OPTION_DEDICATED)
|
|||
find_package(Freetype)
|
||||
find_package(SDL2)
|
||||
find_package(Fluidsynth)
|
||||
find_package(ALSA)
|
||||
if(Freetype_FOUND)
|
||||
find_package(Fontconfig)
|
||||
endif()
|
||||
|
@ -326,6 +327,7 @@ if(NOT OPTION_DEDICATED)
|
|||
link_package(ICU_i18n)
|
||||
link_package(ICU_uc)
|
||||
link_package(OpusFile TARGET OpusFile::opusfile)
|
||||
link_package(ALSA)
|
||||
|
||||
if(SDL2_FOUND AND OPENGL_FOUND AND UNIX)
|
||||
# SDL2 dynamically loads OpenGL if needed, so do not link to OpenGL when
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
#[=======================================================================[.rst:
|
||||
FindALSA
|
||||
--------
|
||||
|
||||
Find Advanced Linux Sound Architecture (ALSA)
|
||||
|
||||
Find the alsa libraries (``asound``)
|
||||
|
||||
IMPORTED Targets
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
.. versionadded:: 3.12
|
||||
|
||||
This module defines :prop_tgt:`IMPORTED` target ``ALSA::ALSA``, if
|
||||
ALSA has been found.
|
||||
|
||||
Result Variables
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
This module defines the following variables:
|
||||
|
||||
``ALSA_FOUND``
|
||||
True if ALSA_INCLUDE_DIR & ALSA_LIBRARY are found
|
||||
|
||||
``ALSA_LIBRARIES``
|
||||
List of libraries when using ALSA.
|
||||
|
||||
``ALSA_INCLUDE_DIRS``
|
||||
Where to find the ALSA headers.
|
||||
|
||||
Cache variables
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
The following cache variables may also be set:
|
||||
|
||||
``ALSA_INCLUDE_DIR``
|
||||
the ALSA include directory
|
||||
|
||||
``ALSA_LIBRARY``
|
||||
the absolute path of the asound library
|
||||
#]=======================================================================]
|
||||
|
||||
find_package(PkgConfig QUIET)
|
||||
pkg_check_modules(PC_ALSA QUIET alsa)
|
||||
|
||||
find_path(ALSA_INCLUDE_DIR NAMES alsa/asoundlib.h
|
||||
DOC "The ALSA (asound) include directory"
|
||||
)
|
||||
|
||||
find_library(ALSA_LIBRARY NAMES asound
|
||||
DOC "The ALSA (asound) library"
|
||||
)
|
||||
|
||||
if(ALSA_INCLUDE_DIR AND EXISTS "${ALSA_INCLUDE_DIR}/alsa/version.h")
|
||||
file(STRINGS "${ALSA_INCLUDE_DIR}/alsa/version.h" alsa_version_str REGEX "^#define[\t ]+SND_LIB_VERSION_STR[\t ]+\".*\"")
|
||||
|
||||
string(REGEX REPLACE "^.*SND_LIB_VERSION_STR[\t ]+\"([^\"]*)\".*$" "\\1" ALSA_VERSION_STRING "${alsa_version_str}")
|
||||
unset(alsa_version_str)
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(ALSA
|
||||
REQUIRED_VARS ALSA_LIBRARY ALSA_INCLUDE_DIR
|
||||
VERSION_VAR ALSA_VERSION_STRING)
|
||||
|
||||
if(ALSA_FOUND)
|
||||
set( ALSA_LIBRARIES ${ALSA_LIBRARY} )
|
||||
set( ALSA_INCLUDE_DIRS ${ALSA_INCLUDE_DIR} )
|
||||
if(NOT TARGET ALSA::ALSA)
|
||||
add_library(ALSA::ALSA UNKNOWN IMPORTED)
|
||||
set_target_properties(ALSA::ALSA PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${ALSA_INCLUDE_DIRS}")
|
||||
set_property(TARGET ALSA::ALSA APPEND PROPERTY IMPORTED_LOCATION "${ALSA_LIBRARY}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
mark_as_advanced(ALSA_INCLUDE_DIR ALSA_LIBRARY)
|
|
@ -7,3 +7,4 @@ add_subdirectory(squirrel)
|
|||
add_subdirectory(nlohmann)
|
||||
add_subdirectory(opengl)
|
||||
add_subdirectory(openttd_social_integration_api)
|
||||
add_subdirectory(midifile)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,161 @@
|
|||
//
|
||||
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
|
||||
// Creation Date: Mon Feb 16 12:26:32 PST 2015 Adapted from binasc program.
|
||||
// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std;
|
||||
// Filename: midifile/include/Binasc.h
|
||||
// Website: http://midifile.sapp.org
|
||||
// Syntax: C++11
|
||||
// vim: ts=3 noexpandtab
|
||||
//
|
||||
// description: Interface to convert bytes between binary and ASCII forms.
|
||||
//
|
||||
|
||||
#ifndef _BINASC_H_INCLUDED
|
||||
#define _BINASC_H_INCLUDED
|
||||
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace smf {
|
||||
|
||||
typedef unsigned char uchar;
|
||||
typedef unsigned short ushort;
|
||||
typedef unsigned long ulong;
|
||||
|
||||
class Binasc {
|
||||
|
||||
public:
|
||||
Binasc (void);
|
||||
|
||||
~Binasc ();
|
||||
|
||||
// functions for setting options:
|
||||
int setLineLength (int length);
|
||||
int getLineLength (void);
|
||||
int setLineBytes (int length);
|
||||
int getLineBytes (void);
|
||||
void setComments (int state);
|
||||
void setCommentsOn (void);
|
||||
void setCommentsOff (void);
|
||||
int getComments (void);
|
||||
void setBytes (int state);
|
||||
void setBytesOn (void);
|
||||
void setBytesOff (void);
|
||||
int getBytes (void);
|
||||
void setMidi (int state);
|
||||
void setMidiOn (void);
|
||||
void setMidiOff (void);
|
||||
int getMidi (void);
|
||||
|
||||
// functions for converting into a binary file:
|
||||
int writeToBinary (const std::string& outfile,
|
||||
const std::string& infile);
|
||||
int writeToBinary (const std::string& outfile,
|
||||
std::istream& input);
|
||||
int writeToBinary (std::ostream& out,
|
||||
const std::string& infile);
|
||||
int writeToBinary (std::ostream& out,
|
||||
std::istream& input);
|
||||
|
||||
// functions for converting into an ASCII file with hex bytes:
|
||||
int readFromBinary (const std::string&
|
||||
outfile,
|
||||
const std::string& infile);
|
||||
int readFromBinary (const std::string& outfile,
|
||||
std::istream& input);
|
||||
int readFromBinary (std::ostream& out,
|
||||
const std::string& infile);
|
||||
int readFromBinary (std::ostream& out,
|
||||
std::istream& input);
|
||||
|
||||
// static functions for writing ordered bytes:
|
||||
static std::ostream& writeLittleEndianUShort (std::ostream& out,
|
||||
ushort value);
|
||||
static std::ostream& writeBigEndianUShort (std::ostream& out,
|
||||
ushort value);
|
||||
static std::ostream& writeLittleEndianShort (std::ostream& out,
|
||||
short value);
|
||||
static std::ostream& writeBigEndianShort (std::ostream& out,
|
||||
short value);
|
||||
static std::ostream& writeLittleEndianULong (std::ostream& out,
|
||||
ulong value);
|
||||
static std::ostream& writeBigEndianULong (std::ostream& out,
|
||||
ulong value);
|
||||
static std::ostream& writeLittleEndianLong (std::ostream& out,
|
||||
long value);
|
||||
static std::ostream& writeBigEndianLong (std::ostream& out,
|
||||
long value);
|
||||
static std::ostream& writeLittleEndianFloat (std::ostream& out,
|
||||
float value);
|
||||
static std::ostream& writeBigEndianFloat (std::ostream& out,
|
||||
float value);
|
||||
static std::ostream& writeLittleEndianDouble (std::ostream& out,
|
||||
double value);
|
||||
static std::ostream& writeBigEndianDouble (std::ostream& out,
|
||||
double value);
|
||||
|
||||
static std::string keyToPitchName (int key);
|
||||
|
||||
protected:
|
||||
int m_bytesQ; // option for printing hex bytes in ASCII output.
|
||||
int m_commentsQ; // option for printing comments in ASCII output.
|
||||
int m_midiQ; // output ASCII data as parsed MIDI file.
|
||||
int m_maxLineLength;// number of character in ASCII output on a line.
|
||||
int m_maxLineBytes; // number of hex bytes in ASCII output on a line.
|
||||
|
||||
private:
|
||||
// helper functions for reading ASCII content to conver to binary:
|
||||
int processLine (std::ostream& out,
|
||||
const std::string& input,
|
||||
int lineNum);
|
||||
int processAsciiWord (std::ostream& out,
|
||||
const std::string& input,
|
||||
int lineNum);
|
||||
int processStringWord (std::ostream& out,
|
||||
const std::string& input,
|
||||
int lineNum);
|
||||
int processBinaryWord (std::ostream& out,
|
||||
const std::string& input,
|
||||
int lineNum);
|
||||
int processDecimalWord (std::ostream& out,
|
||||
const std::string& input,
|
||||
int lineNum);
|
||||
int processHexWord (std::ostream& out,
|
||||
const std::string& input,
|
||||
int lineNum);
|
||||
int processVlvWord (std::ostream& out,
|
||||
const std::string& input,
|
||||
int lineNum);
|
||||
int processMidiPitchBendWord(std::ostream& out,
|
||||
const std::string& input,
|
||||
int lineNum);
|
||||
int processMidiTempoWord (std::ostream& out,
|
||||
const std::string& input,
|
||||
int lineNum);
|
||||
|
||||
// helper functions for reading binary content to convert to ASCII:
|
||||
int outputStyleAscii (std::ostream& out, std::istream& input);
|
||||
int outputStyleBinary (std::ostream& out, std::istream& input);
|
||||
int outputStyleBoth (std::ostream& out, std::istream& input);
|
||||
int outputStyleMidi (std::ostream& out, std::istream& input);
|
||||
|
||||
// MIDI parsing helper functions:
|
||||
int readMidiEvent (std::ostream& out, std::istream& infile,
|
||||
int& trackbytes, int& command);
|
||||
int getVLV (std::istream& infile, int& trackbytes);
|
||||
int getWord (std::string& word, const std::string& input,
|
||||
const std::string& terminators, int index);
|
||||
|
||||
static const char *GMinstrument[128];
|
||||
|
||||
};
|
||||
|
||||
} // end of namespace smf
|
||||
|
||||
#endif /* _BINASC_H_INCLUDED */
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
add_files(
|
||||
Binasc.cpp
|
||||
Binasc.h
|
||||
MidiEvent.cpp
|
||||
MidiEvent.h
|
||||
MidiEventList.cpp
|
||||
MidiEventList.h
|
||||
MidiFile.cpp
|
||||
MidiFile.h
|
||||
MidiMessage.cpp
|
||||
MidiMessage.h
|
||||
)
|
|
@ -0,0 +1,22 @@
|
|||
Copyright (c) 1999-2018, Craig Stuart Sapp
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
and the following disclaimer in the documentation and/or other materials
|
||||
provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,298 @@
|
|||
//
|
||||
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
|
||||
// Creation Date: Sat Feb 14 21:40:14 PST 2015
|
||||
// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std;
|
||||
// Filename: midifile/src/MidiEvent.cpp
|
||||
// Website: http://midifile.sapp.org
|
||||
// Syntax: C++11
|
||||
// vim: ts=3 noexpandtab
|
||||
//
|
||||
// Description: A class which stores a MidiMessage and a timestamp
|
||||
// for the MidiFile class.
|
||||
//
|
||||
|
||||
#include "MidiEvent.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
|
||||
namespace smf {
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEvent::MidiEvent -- Constructor classes
|
||||
//
|
||||
|
||||
MidiEvent::MidiEvent(void) : MidiMessage() {
|
||||
clearVariables();
|
||||
}
|
||||
|
||||
|
||||
MidiEvent::MidiEvent(int command) : MidiMessage(command) {
|
||||
clearVariables();
|
||||
}
|
||||
|
||||
|
||||
MidiEvent::MidiEvent(int command, int p1) : MidiMessage(command, p1) {
|
||||
clearVariables();
|
||||
}
|
||||
|
||||
|
||||
MidiEvent::MidiEvent(int command, int p1, int p2)
|
||||
: MidiMessage(command, p1, p2) {
|
||||
clearVariables();
|
||||
}
|
||||
|
||||
|
||||
MidiEvent::MidiEvent(int aTime, int aTrack, vector<uchar>& message)
|
||||
: MidiMessage(message) {
|
||||
track = aTrack;
|
||||
tick = aTime;
|
||||
seconds = 0.0;
|
||||
seq = 0;
|
||||
m_eventlink = NULL;
|
||||
}
|
||||
|
||||
|
||||
MidiEvent::MidiEvent(const MidiEvent& mfevent) : MidiMessage() {
|
||||
track = mfevent.track;
|
||||
tick = mfevent.tick;
|
||||
seconds = mfevent.seconds;
|
||||
seq = mfevent.seq;
|
||||
m_eventlink = NULL;
|
||||
|
||||
this->resize(mfevent.size());
|
||||
for (int i=0; i<(int)this->size(); i++) {
|
||||
(*this)[i] = mfevent[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEvent::~MidiEvent -- MidiFile Event destructor
|
||||
//
|
||||
|
||||
MidiEvent::~MidiEvent() {
|
||||
track = -1;
|
||||
tick = -1;
|
||||
seconds = -1.0;
|
||||
seq = -1;
|
||||
this->resize(0);
|
||||
m_eventlink = NULL;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEvent::clearVariables -- Clear everything except MidiMessage data.
|
||||
//
|
||||
|
||||
void MidiEvent::clearVariables(void) {
|
||||
track = 0;
|
||||
tick = 0;
|
||||
seconds = 0.0;
|
||||
seq = 0;
|
||||
m_eventlink = NULL;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEvent::operator= -- Copy the contents of another MidiEvent.
|
||||
//
|
||||
|
||||
MidiEvent& MidiEvent::operator=(const MidiEvent& mfevent) {
|
||||
if (this == &mfevent) {
|
||||
return *this;
|
||||
}
|
||||
tick = mfevent.tick;
|
||||
track = mfevent.track;
|
||||
seconds = mfevent.seconds;
|
||||
seq = mfevent.seq;
|
||||
m_eventlink = NULL;
|
||||
this->resize(mfevent.size());
|
||||
for (int i=0; i<(int)this->size(); i++) {
|
||||
(*this)[i] = mfevent[i];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
MidiEvent& MidiEvent::operator=(const MidiMessage& message) {
|
||||
if (this == &message) {
|
||||
return *this;
|
||||
}
|
||||
clearVariables();
|
||||
this->resize(message.size());
|
||||
for (int i=0; i<(int)this->size(); i++) {
|
||||
(*this)[i] = message[i];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
MidiEvent& MidiEvent::operator=(const vector<uchar>& bytes) {
|
||||
clearVariables();
|
||||
this->resize(bytes.size());
|
||||
for (int i=0; i<(int)this->size(); i++) {
|
||||
(*this)[i] = bytes[i];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
MidiEvent& MidiEvent::operator=(const vector<char>& bytes) {
|
||||
clearVariables();
|
||||
setMessage(bytes);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
MidiEvent& MidiEvent::operator=(const vector<int>& bytes) {
|
||||
clearVariables();
|
||||
setMessage(bytes);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEvent::unlinkEvent -- Disassociate this event with another.
|
||||
// Also tell the other event to disassociate from this event.
|
||||
//
|
||||
|
||||
void MidiEvent::unlinkEvent(void) {
|
||||
if (m_eventlink == NULL) {
|
||||
return;
|
||||
}
|
||||
MidiEvent* mev = m_eventlink;
|
||||
m_eventlink = NULL;
|
||||
mev->unlinkEvent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEvent::linkEvent -- Make a link between two messages.
|
||||
// Unlinking
|
||||
//
|
||||
|
||||
void MidiEvent::linkEvent(MidiEvent* mev) {
|
||||
if (mev->m_eventlink != NULL) {
|
||||
// unlink other event if it is linked to something else;
|
||||
mev->unlinkEvent();
|
||||
}
|
||||
// if this is already linked to something else, then unlink:
|
||||
if (m_eventlink != NULL) {
|
||||
m_eventlink->unlinkEvent();
|
||||
}
|
||||
unlinkEvent();
|
||||
|
||||
mev->m_eventlink = this;
|
||||
m_eventlink = mev;
|
||||
}
|
||||
|
||||
|
||||
void MidiEvent::linkEvent(MidiEvent& mev) {
|
||||
linkEvent(&mev);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEvent::getLinkedEvent -- Returns a linked event. Usually
|
||||
// this is the note-off message for a note-on message and vice-versa.
|
||||
// Returns null if there are no links.
|
||||
//
|
||||
|
||||
MidiEvent* MidiEvent::getLinkedEvent(void) {
|
||||
return m_eventlink;
|
||||
}
|
||||
|
||||
|
||||
const MidiEvent* MidiEvent::getLinkedEvent(void) const {
|
||||
return m_eventlink;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEvent::isLinked -- Returns true if there is an event which is not
|
||||
// NULL. This function is similar to getLinkedEvent().
|
||||
//
|
||||
|
||||
int MidiEvent::isLinked(void) const {
|
||||
return m_eventlink == NULL ? 0 : 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEvent::getTickDuration -- For linked events (note-ons and note-offs),
|
||||
// return the absolute tick time difference between the two events.
|
||||
// The tick values are presumed to be in absolute tick mode rather than
|
||||
// delta tick mode. Returns 0 if not linked.
|
||||
//
|
||||
|
||||
int MidiEvent::getTickDuration(void) const {
|
||||
const MidiEvent* mev = getLinkedEvent();
|
||||
if (mev == NULL) {
|
||||
return 0;
|
||||
}
|
||||
int tick2 = mev->tick;
|
||||
if (tick2 > tick) {
|
||||
return tick2 - tick;
|
||||
} else {
|
||||
return tick - tick2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEvent::getDurationInSeconds -- For linked events (note-ons and
|
||||
// note-offs), return the duration of the note in seconds. The
|
||||
// seconds analysis must be done first; otherwise the duration will be
|
||||
// reported as zero.
|
||||
//
|
||||
|
||||
double MidiEvent::getDurationInSeconds(void) const {
|
||||
const MidiEvent* mev = getLinkedEvent();
|
||||
if (mev == NULL) {
|
||||
return 0;
|
||||
}
|
||||
double seconds2 = mev->seconds;
|
||||
if (seconds2 > seconds) {
|
||||
return seconds2 - seconds;
|
||||
} else {
|
||||
return seconds - seconds2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// operator<<(MidiMessage) -- Print tick value followed by MIDI bytes for event.
|
||||
// The tick value will be either relative or absolute depending on the state
|
||||
// of the MidiFile object containing it.
|
||||
//
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, MidiEvent& event) {
|
||||
out << event.tick << '(' << static_cast<MidiMessage&>(event) << ')';
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
} // end namespace smf
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
//
|
||||
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
|
||||
// Creation Date: Sat Feb 14 21:47:39 PST 2015
|
||||
// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std;
|
||||
// Filename: midifile/include/MidiEvent.h
|
||||
// Website: http://midifile.sapp.org
|
||||
// Syntax: C++11
|
||||
// vim: ts=3 noexpandtab
|
||||
//
|
||||
// Description: A class which stores a MidiMessage and a timestamp
|
||||
// for the MidiFile class.
|
||||
//
|
||||
|
||||
#ifndef _MIDIEVENT_H_INCLUDED
|
||||
#define _MIDIEVENT_H_INCLUDED
|
||||
|
||||
#include "MidiMessage.h"
|
||||
|
||||
#include <ostream>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace smf {
|
||||
|
||||
class MidiEvent : public MidiMessage {
|
||||
public:
|
||||
MidiEvent (void);
|
||||
MidiEvent (int command);
|
||||
MidiEvent (int command, int param1);
|
||||
MidiEvent (int command, int param1, int param2);
|
||||
MidiEvent (const MidiMessage& message);
|
||||
MidiEvent (const MidiEvent& mfevent);
|
||||
MidiEvent (int aTime, int aTrack,
|
||||
std::vector<uchar>& message);
|
||||
|
||||
~MidiEvent ();
|
||||
|
||||
MidiEvent& operator= (const MidiEvent& mfevent);
|
||||
MidiEvent& operator= (const MidiMessage& message);
|
||||
MidiEvent& operator= (const std::vector<uchar>& bytes);
|
||||
MidiEvent& operator= (const std::vector<char>& bytes);
|
||||
MidiEvent& operator= (const std::vector<int>& bytes);
|
||||
|
||||
void clearVariables (void);
|
||||
|
||||
// functions related to event linking (note-ons to note-offs).
|
||||
void unlinkEvent (void);
|
||||
void unlinkEvents (void);
|
||||
void linkEvent (MidiEvent* mev);
|
||||
void linkEvents (MidiEvent* mev);
|
||||
void linkEvent (MidiEvent& mev);
|
||||
void linkEvents (MidiEvent& mev);
|
||||
int isLinked (void) const;
|
||||
MidiEvent* getLinkedEvent (void);
|
||||
const MidiEvent* getLinkedEvent (void) const;
|
||||
int getTickDuration (void) const;
|
||||
double getDurationInSeconds (void) const;
|
||||
|
||||
int tick; // delta or absolute MIDI ticks
|
||||
int track; // [original] track number of event in MIDI file
|
||||
double seconds; // calculated time in sec. (after doTimeAnalysis())
|
||||
int seq; // sorting sequence number of event
|
||||
|
||||
private:
|
||||
MidiEvent* m_eventlink; // used to match note-ons and note-offs
|
||||
|
||||
};
|
||||
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, MidiEvent& event);
|
||||
|
||||
|
||||
} // end of namespace smf
|
||||
|
||||
#endif /* _MIDIEVENT_H_INCLUDED */
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,623 @@
|
|||
//
|
||||
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
|
||||
// Creation Date: Sat Feb 14 21:55:38 PST 2015
|
||||
// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std;
|
||||
// Filename: midifile/src/MidiEventList.cpp
|
||||
// Website: http://midifile.sapp.org
|
||||
// Syntax: C++11
|
||||
// vim: ts=3 noexpandtab
|
||||
//
|
||||
// Description: A class which stores a MidiEvents for a MidiFile track.
|
||||
//
|
||||
|
||||
#include "MidiEventList.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace smf {
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEventList::MidiEventList -- Constructor.
|
||||
//
|
||||
|
||||
MidiEventList::MidiEventList(void) {
|
||||
reserve(1000);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEventList::MidiEventList(MidiEventList&) -- Copy constructor.
|
||||
//
|
||||
|
||||
MidiEventList::MidiEventList(const MidiEventList& other) {
|
||||
list.reserve(other.list.size());
|
||||
auto it = other.list.begin();
|
||||
std::generate_n(std::back_inserter(list), other.list.size(), [&]() -> MidiEvent* {
|
||||
return new MidiEvent(**it++);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEventList::MidiEventList(MidiEventList&&) -- Move constructor.
|
||||
//
|
||||
|
||||
MidiEventList::MidiEventList(MidiEventList&& other) {
|
||||
list = std::move(other.list);
|
||||
other.list.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEventList::~MidiEventList -- Deconstructor. Deallocate all stored
|
||||
// data.
|
||||
//
|
||||
|
||||
MidiEventList::~MidiEventList() {
|
||||
clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEventList::operator[] --
|
||||
//
|
||||
|
||||
MidiEvent& MidiEventList::operator[](int index) {
|
||||
return *list[index];
|
||||
}
|
||||
|
||||
|
||||
const MidiEvent& MidiEventList::operator[](int index) const {
|
||||
return *list[index];
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEventList::back -- Return the last element in the list.
|
||||
//
|
||||
|
||||
MidiEvent& MidiEventList::back(void) {
|
||||
return *list.back();
|
||||
}
|
||||
|
||||
|
||||
const MidiEvent& MidiEventList::back(void) const {
|
||||
return *list.back();
|
||||
}
|
||||
|
||||
//
|
||||
// MidiEventList::last -- Alias for MidiEventList::back().
|
||||
//
|
||||
|
||||
MidiEvent& MidiEventList::last(void) {
|
||||
return back();
|
||||
}
|
||||
|
||||
|
||||
const MidiEvent& MidiEventList::last(void) const {
|
||||
return back();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEventList::getEvent -- The same thing as operator[], for
|
||||
// internal use when operator[] would look more messy.
|
||||
//
|
||||
|
||||
MidiEvent& MidiEventList::getEvent(int index) {
|
||||
return *list[index];
|
||||
}
|
||||
|
||||
|
||||
const MidiEvent& MidiEventList::getEvent(int index) const {
|
||||
return *list[index];
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEventList::clear -- De-allocate any MidiEvents present in the list
|
||||
// and set the size of the list to 0.
|
||||
//
|
||||
|
||||
void MidiEventList::clear(void) {
|
||||
for (auto& item : list) {
|
||||
if (item != NULL) {
|
||||
delete item;
|
||||
item = NULL;
|
||||
}
|
||||
}
|
||||
list.resize(0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEventList::data -- Return the low-level array of MidiMessage
|
||||
// pointers. This is useful for applying your own sorting
|
||||
// function to the list.
|
||||
//
|
||||
|
||||
MidiEvent** MidiEventList::data(void) {
|
||||
return list.data();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEventList::reserve -- Pre-allocate space in the list for storing
|
||||
// elements.
|
||||
//
|
||||
|
||||
void MidiEventList::reserve(int rsize) {
|
||||
if (rsize > (int)list.size()) {
|
||||
list.reserve(rsize);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEventList::getSize -- Return the number of MidiEvents stored
|
||||
// in the list.
|
||||
//
|
||||
|
||||
int MidiEventList::getSize(void) const {
|
||||
return (int)list.size();
|
||||
}
|
||||
|
||||
//
|
||||
// MidiEventList::size -- Alias for MidiEventList::getSize().
|
||||
//
|
||||
|
||||
int MidiEventList::size(void) const {
|
||||
return getSize();
|
||||
}
|
||||
|
||||
//
|
||||
// MidiEventList::getEventCount -- Alias for MidiEventList::getSize().
|
||||
//
|
||||
|
||||
int MidiEventList::getEventCount(void) const {
|
||||
return getSize();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEventList::append -- add a MidiEvent at the end of the list. Returns
|
||||
// the index of the appended event.
|
||||
//
|
||||
|
||||
int MidiEventList::append(MidiEvent& event) {
|
||||
MidiEvent* ptr = new MidiEvent(event);
|
||||
list.push_back(ptr);
|
||||
return (int)list.size()-1;
|
||||
}
|
||||
|
||||
//
|
||||
// MidiEventList::push -- Alias for MidiEventList::append().
|
||||
//
|
||||
|
||||
int MidiEventList::push(MidiEvent& event) {
|
||||
return append(event);
|
||||
}
|
||||
|
||||
//
|
||||
// MidiEventList::push_back -- Alias for MidiEventList::append().
|
||||
//
|
||||
|
||||
int MidiEventList::push_back(MidiEvent& event) {
|
||||
return append(event);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEventList::removeEmpties -- Remove any MIDI message which contain no
|
||||
// bytes. This function first deallocates any empty MIDI events, and then
|
||||
// removes them from the list of events.
|
||||
//
|
||||
|
||||
void MidiEventList::removeEmpties(void) {
|
||||
int count = 0;
|
||||
for (auto& item : list) {
|
||||
if (item->empty()) {
|
||||
delete item;
|
||||
item = NULL;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
std::vector<MidiEvent*> newlist;
|
||||
newlist.reserve(list.size() - count);
|
||||
for (auto& item : list) {
|
||||
if (item) {
|
||||
newlist.push_back(item);
|
||||
}
|
||||
}
|
||||
list.swap(newlist);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEventList::linkNotePairs -- Match note-ones and note-offs together
|
||||
// There are two models that can be done if two notes are overlapping
|
||||
// on the same pitch: the first note-off affects the last note-on,
|
||||
// or the first note-off affects the first note-on. Currently the
|
||||
// first note-off affects the last note-on, but both methods could
|
||||
// be implemented with user selectability. The current state of the
|
||||
// track is assumed to be in time-sorted order. Returns the number
|
||||
// of linked notes (note-on/note-off pairs).
|
||||
//
|
||||
|
||||
int MidiEventList::linkEventPairs(void) {
|
||||
return linkNotePairs();
|
||||
}
|
||||
|
||||
|
||||
int MidiEventList::linkNotePairs(void) {
|
||||
|
||||
// Note-on states:
|
||||
// dimension 1: MIDI channel (0-15)
|
||||
// dimension 2: MIDI key (0-127) (but 0 not used for note-ons)
|
||||
// dimension 3: List of active note-ons or note-offs.
|
||||
std::vector<std::vector<std::vector<MidiEvent*>>> noteons;
|
||||
noteons.resize(16);
|
||||
for (auto& noteon : noteons) {
|
||||
noteon.resize(128);
|
||||
}
|
||||
|
||||
// Controller linking: The following General MIDI controller numbers are
|
||||
// also monitored for linking within the track (but not between tracks).
|
||||
// hex dec name range
|
||||
// 40 64 Hold pedal (Sustain) on/off 0..63=off 64..127=on
|
||||
// 41 65 Portamento on/off 0..63=off 64..127=on
|
||||
// 42 66 Sustenuto Pedal on/off 0..63=off 64..127=on
|
||||
// 43 67 Soft Pedal on/off 0..63=off 64..127=on
|
||||
// 44 68 Legato Pedal on/off 0..63=off 64..127=on
|
||||
// 45 69 Hold Pedal 2 on/off 0..63=off 64..127=on
|
||||
// 50 80 General Purpose Button 0..63=off 64..127=on
|
||||
// 51 81 General Purpose Button 0..63=off 64..127=on
|
||||
// 52 82 General Purpose Button 0..63=off 64..127=on
|
||||
// 53 83 General Purpose Button 0..63=off 64..127=on
|
||||
// 54 84 Undefined on/off 0..63=off 64..127=on
|
||||
// 55 85 Undefined on/off 0..63=off 64..127=on
|
||||
// 56 86 Undefined on/off 0..63=off 64..127=on
|
||||
// 57 87 Undefined on/off 0..63=off 64..127=on
|
||||
// 58 88 Undefined on/off 0..63=off 64..127=on
|
||||
// 59 89 Undefined on/off 0..63=off 64..127=on
|
||||
// 5A 90 Undefined on/off 0..63=off 64..127=on
|
||||
// 7A 122 Local Keyboard On/Off 0..63=off 64..127=on
|
||||
|
||||
// first keep track of whether the controller is an on/off switch:
|
||||
std::vector<std::pair<int, int>> contmap;
|
||||
contmap.resize(128);
|
||||
std::pair<int, int> zero(0, 0);
|
||||
std::fill(contmap.begin(), contmap.end(), zero);
|
||||
contmap[64].first = 1; contmap[64].second = 0;
|
||||
contmap[65].first = 1; contmap[65].second = 1;
|
||||
contmap[66].first = 1; contmap[66].second = 2;
|
||||
contmap[67].first = 1; contmap[67].second = 3;
|
||||
contmap[68].first = 1; contmap[68].second = 4;
|
||||
contmap[69].first = 1; contmap[69].second = 5;
|
||||
contmap[80].first = 1; contmap[80].second = 6;
|
||||
contmap[81].first = 1; contmap[81].second = 7;
|
||||
contmap[82].first = 1; contmap[82].second = 8;
|
||||
contmap[83].first = 1; contmap[83].second = 9;
|
||||
contmap[84].first = 1; contmap[84].second = 10;
|
||||
contmap[85].first = 1; contmap[85].second = 11;
|
||||
contmap[86].first = 1; contmap[86].second = 12;
|
||||
contmap[87].first = 1; contmap[87].second = 13;
|
||||
contmap[88].first = 1; contmap[88].second = 14;
|
||||
contmap[89].first = 1; contmap[89].second = 15;
|
||||
contmap[90].first = 1; contmap[90].second = 16;
|
||||
contmap[122].first = 1; contmap[122].second = 17;
|
||||
|
||||
// dimensions:
|
||||
// 1: mapped controller (0 to 17)
|
||||
// 2: channel (0 to 15)
|
||||
std::vector<std::vector<MidiEvent*>> contevents;
|
||||
contevents.resize(18);
|
||||
std::vector<std::vector<int>> oldstates;
|
||||
oldstates.resize(18);
|
||||
for (int i=0; i<18; i++) {
|
||||
contevents[i].resize(16);
|
||||
std::fill(contevents[i].begin(), contevents[i].end(), nullptr);
|
||||
oldstates[i].resize(16);
|
||||
std::fill(oldstates[i].begin(), oldstates[i].end(), -1);
|
||||
}
|
||||
|
||||
// Now iterate through the MidiEventList keeping track of note and
|
||||
// select controller states and linking notes/controllers as needed.
|
||||
int channel;
|
||||
int key;
|
||||
int contnum;
|
||||
int contval;
|
||||
int conti;
|
||||
int contstate;
|
||||
int counter = 0;
|
||||
MidiEvent* mev;
|
||||
MidiEvent* noteon;
|
||||
for (int i=0; i<getSize(); i++) {
|
||||
mev = &getEvent(i);
|
||||
mev->unlinkEvent();
|
||||
if (mev->isNoteOn()) {
|
||||
// store the note-on to pair later with a note-off message.
|
||||
key = mev->getKeyNumber();
|
||||
channel = mev->getChannel();
|
||||
noteons[channel][key].push_back(mev);
|
||||
} else if (mev->isNoteOff()) {
|
||||
key = mev->getKeyNumber();
|
||||
channel = mev->getChannel();
|
||||
if (noteons[channel][key].size() > 0) {
|
||||
noteon = noteons[channel][key].back();
|
||||
noteons[channel][key].pop_back();
|
||||
noteon->linkEvent(mev);
|
||||
counter++;
|
||||
}
|
||||
} else if (mev->isController()) {
|
||||
contnum = mev->getP1();
|
||||
if (contmap[contnum].first) {
|
||||
conti = contmap[contnum].second;
|
||||
channel = mev->getChannel();
|
||||
contval = mev->getP2();
|
||||
contstate = contval < 64 ? 0 : 1;
|
||||
if ((oldstates[conti][channel] == -1) && contstate) {
|
||||
// a newly initialized onstate was detected, so store for
|
||||
// later linking to an off state.
|
||||
contevents[conti][channel] = mev;
|
||||
oldstates[conti][channel] = contstate;
|
||||
} else if (oldstates[conti][channel] == contstate) {
|
||||
// the controller state is redundant and will be ignored.
|
||||
} else if ((oldstates[conti][channel] == 0) && contstate) {
|
||||
// controller is currently off, so store on-state for next link
|
||||
contevents[conti][channel] = mev;
|
||||
oldstates[conti][channel] = contstate;
|
||||
} else if ((oldstates[conti][channel] == 1) && (contstate == 0)) {
|
||||
// controller has just been turned off, so link to
|
||||
// stored on-message.
|
||||
contevents[conti][channel]->linkEvent(mev);
|
||||
oldstates[conti][channel] = contstate;
|
||||
// not necessary, but maybe use for something later:
|
||||
contevents[conti][channel] = mev;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return counter;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEventList::clearLinks -- remove all note-on/note-off links.
|
||||
//
|
||||
|
||||
void MidiEventList::clearLinks(void) {
|
||||
for (int i=0; i<(int)getSize(); i++) {
|
||||
getEvent(i).unlinkEvent();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEventList::clearSequence -- Remove any sequence serial numbers from
|
||||
// MidiEvents in the list. This will cause the default ordering by
|
||||
// sortTracks() to be used, in which case the ordering of MidiEvents
|
||||
// occurring at the same tick may switch their ordering.
|
||||
//
|
||||
|
||||
void MidiEventList::clearSequence(void) {
|
||||
for (int i=0; i<getEventCount(); i++) {
|
||||
getEvent(i).seq = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEventList::markSequence -- Assign a sequence serial number to
|
||||
// every MidiEvent in the event list. This is useful if you want
|
||||
// to preseve the order of MIDI messages in a track when they occur
|
||||
// at the same tick time. Particularly for use with joinTracks()
|
||||
// or sortTracks(). markSequence will be done automatically when
|
||||
// a MIDI file is read, in case the ordering of events occurring at
|
||||
// the same time is important. Use clearSequence() to use the
|
||||
// default sorting behavior of sortTracks() when events occur at the
|
||||
// same time. Returns the next serial number that has not yet been
|
||||
// used.
|
||||
// default value: sequence = 1.
|
||||
//
|
||||
|
||||
int MidiEventList::markSequence(int sequence) {
|
||||
for (int i=0; i<getEventCount(); i++) {
|
||||
getEvent(i).seq = sequence++;
|
||||
}
|
||||
return sequence;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// protected functions --
|
||||
//
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEventList::detach -- De-allocate any MidiEvents present in the list
|
||||
// and set the size of the list to 0.
|
||||
//
|
||||
|
||||
|
||||
void MidiEventList::detach(void) {
|
||||
list.resize(0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEventList::push_back_no_copy -- add a MidiEvent at the end of
|
||||
// the list. The event is not copied, but memory from the
|
||||
// remote location is used. Returns the index of the appended event.
|
||||
//
|
||||
|
||||
int MidiEventList::push_back_no_copy(MidiEvent* event) {
|
||||
list.push_back(event);
|
||||
return (int)list.size()-1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEventList::operator=(MidiEventList) -- Assignment.
|
||||
//
|
||||
|
||||
MidiEventList& MidiEventList::operator=(MidiEventList& other) {
|
||||
list.swap(other.list);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// private functions
|
||||
//
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// MidiEventList::sort -- Private because the MidiFile class keeps
|
||||
// track of delta versus absolute tick states of the MidiEventList,
|
||||
// and sorting is only allowed in absolute tick state (The MidiEventList
|
||||
// does not know about delta/absolute tick states of its contents).
|
||||
//
|
||||
|
||||
void MidiEventList::sort(void) {
|
||||
qsort(data(), getEventCount(), sizeof(MidiEvent*), eventcompare);
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// external functions
|
||||
//
|
||||
|
||||
//////////////////////////////
|
||||
//
|
||||
// eventcompare -- Event comparison function for sorting tracks.
|
||||
//
|
||||
// Sorting rules:
|
||||
// (1) sort by (absolute) tick value; otherwise, if tick values are the same:
|
||||
// (2) end-of-track meta message is always last.
|
||||
// (3) other meta-messages come before regular MIDI messages.
|
||||
// (4) note-offs come after all other regular MIDI messages except note-ons.
|
||||
// (5) note-ons come after all other regular MIDI messages.
|
||||
//
|
||||
|
||||
int eventcompare(const void* a, const void* b) {
|
||||
MidiEvent& aevent = **((MidiEvent**)a);
|
||||
MidiEvent& bevent = **((MidiEvent**)b);
|
||||
|
||||
if (aevent.tick > bevent.tick) {
|
||||
// aevent occurs after bevent
|
||||
return +1;
|
||||
} else if (aevent.tick < bevent.tick) {
|
||||
// aevent occurs before bevent
|
||||
return -1;
|
||||
} else if ((aevent.seq != 0) && (bevent.seq != 0) && (aevent.seq > bevent.seq)) {
|
||||
// aevent sequencing state occurs after bevent
|
||||
// see MidiEventList::markSequence()
|
||||
return +1;
|
||||
} else if ((aevent.seq != 0) && (bevent.seq != 0) && (aevent.seq < bevent.seq)) {
|
||||
// aevent sequencing state occurs before bevent
|
||||
// see MidiEventList::markSequence()
|
||||
return -1;
|
||||
} else if (aevent.getP0() == 0xff && aevent.getP1() == 0x2f) {
|
||||
// end-of-track meta-message should always be last (but won't really
|
||||
// matter since the writing function ignores all end-of-track messages
|
||||
// and writes its own.
|
||||
return +1;
|
||||
} else if (bevent.getP0() == 0xff && bevent.getP1() == 0x2f) {
|
||||
// end-of-track meta-message should always be last (but won't really
|
||||
// matter since the writing function ignores all end-of-track messages
|
||||
// and writes its own.
|
||||
return -1;
|
||||
} else if (aevent.getP0() == 0xff && bevent.getP0() != 0xff) {
|
||||
// other meta-messages are placed before real MIDI messages
|
||||
return -1;
|
||||
} else if (aevent.getP0() != 0xff && bevent.getP0() == 0xff) {
|
||||
// other meta-messages are placed before real MIDI messages
|
||||
return +1;
|
||||
} else if (((aevent.getP0() & 0xf0) == 0x90) && (aevent.getP2() != 0)) {
|
||||
// note-ons come after all other types of MIDI messages
|
||||
return +1;
|
||||
} else if (((bevent.getP0() & 0xf0) == 0x90) && (bevent.getP2() != 0)) {
|
||||
// note-ons come after all other types of MIDI messages
|
||||
return -1;
|
||||
} else if (((aevent.getP0() & 0xf0) == 0x90) || ((aevent.getP0() & 0xf0) == 0x80)) {
|
||||
// note-offs come after all other MIDI messages (except note-ons)
|
||||
return +1;
|
||||
} else if (((bevent.getP0() & 0xf0) == 0x90) || ((bevent.getP0() & 0xf0) == 0x80)) {
|
||||
// note-offs come after all other MIDI messages (except note-ons)
|
||||
return -1;
|
||||
} else if (((aevent.getP0() & 0xf0) == 0xb0) && ((bevent.getP0() & 0xf0) == 0xb0)) {
|
||||
// both events are continuous controllers. Sort them by controller number
|
||||
if (aevent.getP1() > bevent.getP1()) {
|
||||
return +1;
|
||||
} if (aevent.getP1() < bevent.getP1()) {
|
||||
return -1;
|
||||
} else {
|
||||
// same controller number, so sort by data value
|
||||
if (aevent.getP2() > bevent.getP2()) {
|
||||
return +1;
|
||||
} if (aevent.getP2() < bevent.getP2()) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // end namespace smf
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
//
|
||||
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
|
||||
// Creation Date: Sat Feb 14 21:55:38 PST 2015
|
||||
// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std;
|
||||
// Filename: midifile/include/MidiEventList.h
|
||||
// Website: http://midifile.sapp.org
|
||||
// Syntax: C++11
|
||||
// vim: ts=3 noexpandtab
|
||||
//
|
||||
// Description: A class that stores a MidiEvents for a MidiFile track.
|
||||
//
|
||||
|
||||
#ifndef _MIDIEVENTLIST_H_INCLUDED
|
||||
#define _MIDIEVENTLIST_H_INCLUDED
|
||||
|
||||
#include "MidiEvent.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace smf {
|
||||
|
||||
class MidiEventList {
|
||||
public:
|
||||
MidiEventList (void);
|
||||
MidiEventList (const MidiEventList& other);
|
||||
MidiEventList (MidiEventList&& other);
|
||||
|
||||
~MidiEventList ();
|
||||
|
||||
MidiEventList& operator= (MidiEventList& other);
|
||||
MidiEvent& operator[] (int index);
|
||||
const MidiEvent& operator[] (int index) const;
|
||||
|
||||
MidiEvent& back (void);
|
||||
const MidiEvent& back (void) const;
|
||||
MidiEvent& last (void);
|
||||
const MidiEvent& last (void) const;
|
||||
MidiEvent& getEvent (int index);
|
||||
const MidiEvent& getEvent (int index) const;
|
||||
void clear (void);
|
||||
void reserve (int rsize);
|
||||
int getEventCount (void) const;
|
||||
int getSize (void) const;
|
||||
int size (void) const;
|
||||
void removeEmpties (void);
|
||||
int linkNotePairs (void);
|
||||
int linkEventPairs (void);
|
||||
void clearLinks (void);
|
||||
void clearSequence (void);
|
||||
int markSequence (int sequence = 1);
|
||||
|
||||
int push (MidiEvent& event);
|
||||
int push_back (MidiEvent& event);
|
||||
int append (MidiEvent& event);
|
||||
|
||||
// careful when using these, intended for internal use in MidiFile class:
|
||||
void detach (void);
|
||||
int push_back_no_copy (MidiEvent* event);
|
||||
|
||||
// access to the list of MidiEvents for sorting with an external function:
|
||||
MidiEvent** data (void);
|
||||
|
||||
protected:
|
||||
std::vector<MidiEvent*> list;
|
||||
|
||||
private:
|
||||
void sort (void);
|
||||
|
||||
// MidiFile class calls sort()
|
||||
friend class MidiFile;
|
||||
};
|
||||
|
||||
|
||||
int eventcompare(const void* a, const void* b);
|
||||
|
||||
} // end of namespace smf
|
||||
|
||||
#endif /* _MIDIEVENTLIST_H_INCLUDED */
|
||||
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,332 @@
|
|||
//
|
||||
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
|
||||
// Creation Date: Fri Nov 26 14:12:01 PST 1999
|
||||
// Last Modified: Mon Jan 18 20:54:04 PST 2021 Added readSmf().
|
||||
// Filename: midifile/include/MidiFile.h
|
||||
// Website: http://midifile.sapp.org
|
||||
// Syntax: C++11
|
||||
// vim: ts=3 noexpandtab
|
||||
//
|
||||
// Description: A class that can read/write Standard MIDI files.
|
||||
// MIDI data is stored by track in an array.
|
||||
//
|
||||
|
||||
#ifndef _MIDIFILE_H_INCLUDED
|
||||
#define _MIDIFILE_H_INCLUDED
|
||||
|
||||
#include "MidiEventList.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <istream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace smf {
|
||||
|
||||
enum {
|
||||
TRACK_STATE_SPLIT = 0, // Tracks are separated into separate vector postions.
|
||||
TRACK_STATE_JOINED = 1 // Tracks are merged into a single vector position,
|
||||
}; // like a Type-0 MIDI file, but reversible.
|
||||
|
||||
enum {
|
||||
TIME_STATE_DELTA = 0, // MidiMessage::ticks are in delta time format (like MIDI file).
|
||||
TIME_STATE_ABSOLUTE = 1 // MidiMessage::ticks are in absolute time format (0=start time).
|
||||
};
|
||||
|
||||
class _TickTime {
|
||||
public:
|
||||
int tick;
|
||||
double seconds;
|
||||
};
|
||||
|
||||
|
||||
class MidiFile {
|
||||
public:
|
||||
MidiFile (void);
|
||||
MidiFile (const std::string& filename);
|
||||
MidiFile (std::istream& input);
|
||||
MidiFile (const MidiFile& other);
|
||||
MidiFile (MidiFile&& other);
|
||||
|
||||
~MidiFile ();
|
||||
|
||||
MidiFile& operator= (const MidiFile& other);
|
||||
MidiFile& operator= (MidiFile&& other);
|
||||
|
||||
// Reading/writing functions:
|
||||
|
||||
// Auto-detected SMF or ASCII-encoded SMF (decoded with Binasc class):
|
||||
bool read (const std::string& filename);
|
||||
bool read (std::istream& instream);
|
||||
bool readBase64 (const std::string& base64data);
|
||||
bool readBase64 (std::istream& instream);
|
||||
|
||||
// Only allow Standard MIDI File input:
|
||||
bool readSmf (const std::string& filename);
|
||||
bool readSmf (std::istream& instream);
|
||||
|
||||
bool write (const std::string& filename);
|
||||
bool write (std::ostream& out);
|
||||
bool writeBase64 (const std::string& out, int width = 0);
|
||||
bool writeBase64 (std::ostream& out, int width = 0);
|
||||
std::string getBase64 (int width = 0);
|
||||
bool writeHex (const std::string& filename, int width = 25);
|
||||
bool writeHex (std::ostream& out, int width = 25);
|
||||
bool writeBinasc (const std::string& filename);
|
||||
bool writeBinasc (std::ostream& out);
|
||||
bool writeBinascWithComments (const std::string& filename);
|
||||
bool writeBinascWithComments (std::ostream& out);
|
||||
bool status (void) const;
|
||||
|
||||
// track-related functions:
|
||||
const MidiEventList& operator[] (int aTrack) const;
|
||||
MidiEventList& operator[] (int aTrack);
|
||||
int getTrackCount (void) const;
|
||||
int getNumTracks (void) const;
|
||||
int size (void) const;
|
||||
void removeEmpties (void);
|
||||
|
||||
// tick-related functions:
|
||||
void makeDeltaTicks (void);
|
||||
void deltaTicks (void);
|
||||
void makeAbsoluteTicks (void);
|
||||
void absoluteTicks (void);
|
||||
int getTickState (void) const;
|
||||
bool isDeltaTicks (void) const;
|
||||
bool isAbsoluteTicks (void) const;
|
||||
|
||||
// join/split track functionality:
|
||||
void joinTracks (void);
|
||||
void splitTracks (void);
|
||||
void splitTracksByChannel (void);
|
||||
int getTrackState (void) const;
|
||||
int hasJoinedTracks (void) const;
|
||||
int hasSplitTracks (void) const;
|
||||
int getSplitTrack (int track, int index) const;
|
||||
int getSplitTrack (int index) const;
|
||||
|
||||
// track sorting funcionality:
|
||||
void sortTrack (int track);
|
||||
void sortTracks (void);
|
||||
void markSequence (void);
|
||||
void markSequence (int track, int sequence = 1);
|
||||
void clearSequence (void);
|
||||
void clearSequence (int track);
|
||||
|
||||
// track manipulation functionality:
|
||||
int addTrack (void);
|
||||
int addTrack (int count);
|
||||
int addTracks (int count);
|
||||
void deleteTrack (int aTrack);
|
||||
void mergeTracks (int aTrack1, int aTrack2);
|
||||
int getTrackCountAsType1 (void);
|
||||
|
||||
// ticks-per-quarter related functions:
|
||||
void setMillisecondTicks (void);
|
||||
int getTicksPerQuarterNote (void) const;
|
||||
int getTPQ (void) const;
|
||||
void setTicksPerQuarterNote (int ticks);
|
||||
void setTPQ (int ticks);
|
||||
|
||||
// physical-time analysis functions:
|
||||
void doTimeAnalysis (void);
|
||||
double getTimeInSeconds (int aTrack, int anIndex);
|
||||
double getTimeInSeconds (int tickvalue);
|
||||
double getAbsoluteTickTime (double starttime);
|
||||
int getFileDurationInTicks (void);
|
||||
double getFileDurationInQuarters (void);
|
||||
double getFileDurationInSeconds (void);
|
||||
|
||||
// note-analysis functions:
|
||||
int linkNotePairs (void);
|
||||
int linkEventPairs (void);
|
||||
void clearLinks (void);
|
||||
|
||||
// filename functions:
|
||||
void setFilename (const std::string& aname);
|
||||
const char* getFilename (void) const;
|
||||
|
||||
// event functionality:
|
||||
MidiEvent* addEvent (int aTrack, int aTick,
|
||||
std::vector<uchar>& midiData);
|
||||
MidiEvent* addEvent (MidiEvent& mfevent);
|
||||
MidiEvent* addEvent (int aTrack, MidiEvent& mfevent);
|
||||
MidiEvent& getEvent (int aTrack, int anIndex);
|
||||
const MidiEvent& getEvent (int aTrack, int anIndex) const;
|
||||
int getEventCount (int aTrack) const;
|
||||
int getNumEvents (int aTrack) const;
|
||||
void allocateEvents (int track, int aSize);
|
||||
void erase (void);
|
||||
void clear (void);
|
||||
void clear_no_deallocate (void);
|
||||
|
||||
// MIDI message adding convenience functions:
|
||||
MidiEvent* addNoteOn (int aTrack, int aTick,
|
||||
int aChannel, int key,
|
||||
int vel);
|
||||
MidiEvent* addNoteOff (int aTrack, int aTick,
|
||||
int aChannel, int key,
|
||||
int vel);
|
||||
MidiEvent* addNoteOff (int aTrack, int aTick,
|
||||
int aChannel, int key);
|
||||
MidiEvent* addController (int aTrack, int aTick,
|
||||
int aChannel, int num,
|
||||
int value);
|
||||
MidiEvent* addPatchChange (int aTrack, int aTick,
|
||||
int aChannel, int patchnum);
|
||||
MidiEvent* addTimbre (int aTrack, int aTick,
|
||||
int aChannel, int patchnum);
|
||||
MidiEvent* addPitchBend (int aTrack, int aTick,
|
||||
int aChannel, double amount);
|
||||
|
||||
// RPN settings:
|
||||
void setPitchBendRange (int aTrack, int aTick,
|
||||
int aChannel, double range);
|
||||
|
||||
// Controller message adding convenience functions:
|
||||
MidiEvent* addSustain (int aTrack, int aTick,
|
||||
int aChannel, int value);
|
||||
MidiEvent* addSustainPedal (int aTrack, int aTick,
|
||||
int aChannel, int value);
|
||||
MidiEvent* addSustainOn (int aTrack, int aTick,
|
||||
int aChannel);
|
||||
MidiEvent* addSustainPedalOn (int aTrack, int aTick,
|
||||
int aChannel);
|
||||
MidiEvent* addSustainOff (int aTrack, int aTick,
|
||||
int aChannel);
|
||||
MidiEvent* addSustainPedalOff (int aTrack, int aTick,
|
||||
int aChannel);
|
||||
|
||||
// Meta-event adding convenience functions:
|
||||
MidiEvent* addMetaEvent (int aTrack, int aTick,
|
||||
int aType,
|
||||
std::vector<uchar>& metaData);
|
||||
MidiEvent* addMetaEvent (int aTrack, int aTick,
|
||||
int aType,
|
||||
const std::string& metaData);
|
||||
MidiEvent* addText (int aTrack, int aTick,
|
||||
const std::string& text);
|
||||
MidiEvent* addCopyright (int aTrack, int aTick,
|
||||
const std::string& text);
|
||||
MidiEvent* addTrackName (int aTrack, int aTick,
|
||||
const std::string& name);
|
||||
MidiEvent* addInstrumentName (int aTrack, int aTick,
|
||||
const std::string& name);
|
||||
MidiEvent* addLyric (int aTrack, int aTick,
|
||||
const std::string& text);
|
||||
MidiEvent* addMarker (int aTrack, int aTick,
|
||||
const std::string& text);
|
||||
MidiEvent* addCue (int aTrack, int aTick,
|
||||
const std::string& text);
|
||||
MidiEvent* addTempo (int aTrack, int aTick,
|
||||
double aTempo);
|
||||
MidiEvent* addKeySignature (int aTrack, int aTick,
|
||||
int fifths, bool mode = 0);
|
||||
MidiEvent* addTimeSignature (int aTrack, int aTick,
|
||||
int top, int bottom,
|
||||
int clocksPerClick = 24,
|
||||
int num32dsPerQuarter = 8);
|
||||
MidiEvent* addCompoundTimeSignature(int aTrack, int aTick,
|
||||
int top, int bottom,
|
||||
int clocksPerClick = 36,
|
||||
int num32dsPerQuarter = 8);
|
||||
|
||||
uchar readByte (std::istream& input);
|
||||
|
||||
// static functions:
|
||||
static ushort readLittleEndian2Bytes (std::istream& input);
|
||||
static ulong readLittleEndian4Bytes (std::istream& input);
|
||||
static std::ostream& writeLittleEndianUShort (std::ostream& out,
|
||||
ushort value);
|
||||
static std::ostream& writeBigEndianUShort (std::ostream& out,
|
||||
ushort value);
|
||||
static std::ostream& writeLittleEndianShort (std::ostream& out,
|
||||
short value);
|
||||
static std::ostream& writeBigEndianShort (std::ostream& out,
|
||||
short value);
|
||||
static std::ostream& writeLittleEndianULong (std::ostream& out,
|
||||
ulong value);
|
||||
static std::ostream& writeBigEndianULong (std::ostream& out,
|
||||
ulong value);
|
||||
static std::ostream& writeLittleEndianLong (std::ostream& out,
|
||||
long value);
|
||||
static std::ostream& writeBigEndianLong (std::ostream& out,
|
||||
long value);
|
||||
static std::ostream& writeLittleEndianFloat (std::ostream& out,
|
||||
float value);
|
||||
static std::ostream& writeBigEndianFloat (std::ostream& out,
|
||||
float value);
|
||||
static std::ostream& writeLittleEndianDouble (std::ostream& out,
|
||||
double value);
|
||||
static std::ostream& writeBigEndianDouble (std::ostream& out,
|
||||
double value);
|
||||
static std::string getGMInstrumentName (int patchIndex);
|
||||
|
||||
protected:
|
||||
// m_events == Lists of MidiEvents for each MIDI file track.
|
||||
std::vector<MidiEventList*> m_events;
|
||||
|
||||
// m_ticksPerQuarterNote == A value for the MIDI file header
|
||||
// which represents the number of ticks in a quarter note
|
||||
// that are used as units for the delta times for MIDI events
|
||||
// in MIDI file track data.
|
||||
int m_ticksPerQuarterNote = 120;
|
||||
|
||||
// m_theTrackState == state variable for whether the tracks
|
||||
// are joined or split.
|
||||
int m_theTrackState = TRACK_STATE_SPLIT;
|
||||
|
||||
// m_theTimeState == state variable for whether the MidiEvent::tick
|
||||
// variable contain absolute ticks since the start of the file's
|
||||
// time, or delta ticks since the last MIDI event in the track.
|
||||
int m_theTimeState = TIME_STATE_ABSOLUTE;
|
||||
|
||||
// m_readFileName == the filename of the last file read into
|
||||
// the object.
|
||||
std::string m_readFileName;
|
||||
|
||||
// m_timemapvalid ==
|
||||
bool m_timemapvalid = false;
|
||||
|
||||
// m_timemap ==
|
||||
std::vector<_TickTime> m_timemap;
|
||||
|
||||
// m_rwstatus == True if last read was successful, false if a problem.
|
||||
bool m_rwstatus = true;
|
||||
|
||||
// m_linkedEventQ == True if link analysis has been done.
|
||||
bool m_linkedEventsQ = false;
|
||||
|
||||
private:
|
||||
int extractMidiData (std::istream& inputfile,
|
||||
std::vector<uchar>& array,
|
||||
uchar& runningCommand);
|
||||
ulong readVLValue (std::istream& inputfile);
|
||||
ulong unpackVLV (uchar a = 0, uchar b = 0,
|
||||
uchar c = 0, uchar d = 0,
|
||||
uchar e = 0);
|
||||
void writeVLValue (long aValue,
|
||||
std::vector<uchar>& data);
|
||||
int makeVLV (uchar *buffer, int number);
|
||||
static int ticksearch (const void* A, const void* B);
|
||||
static int secondsearch (const void* A, const void* B);
|
||||
void buildTimeMap (void);
|
||||
double linearTickInterpolationAtSecond (double seconds);
|
||||
double linearSecondInterpolationAtTick (int ticktime);
|
||||
std::string base64Encode (const std::string &input);
|
||||
std::string base64Decode (const std::string &input);
|
||||
|
||||
static const std::string encodeLookup;
|
||||
static const std::vector<int> decodeLookup;
|
||||
static const char *GMinstrument[128];
|
||||
};
|
||||
|
||||
} // end of namespace smf
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, smf::MidiFile& aMidiFile);
|
||||
|
||||
#endif /* _MIDIFILE_H_INCLUDED */
|
||||
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,219 @@
|
|||
//
|
||||
// Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
|
||||
// Creation Date: Sat Feb 14 20:36:32 PST 2015
|
||||
// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std;
|
||||
// Filename: midifile/include/MidiMessage.h
|
||||
// Website: http://midifile.sapp.org
|
||||
// Syntax: C++11
|
||||
// vim: ts=3 noexpandtab
|
||||
//
|
||||
// Description: Storage for bytes of a MIDI message for use in MidiFile
|
||||
// class.
|
||||
//
|
||||
|
||||
#ifndef _MIDIMESSAGE_H_INCLUDED
|
||||
#define _MIDIMESSAGE_H_INCLUDED
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace smf {
|
||||
|
||||
typedef unsigned char uchar;
|
||||
typedef unsigned short ushort;
|
||||
typedef unsigned long ulong;
|
||||
|
||||
class MidiMessage : public std::vector<uchar> {
|
||||
|
||||
public:
|
||||
MidiMessage (void);
|
||||
MidiMessage (int command);
|
||||
MidiMessage (int command, int p1);
|
||||
MidiMessage (int command, int p1, int p2);
|
||||
MidiMessage (const MidiMessage& message);
|
||||
MidiMessage (const std::vector<uchar>& message);
|
||||
MidiMessage (const std::vector<char>& message);
|
||||
MidiMessage (const std::vector<int>& message);
|
||||
|
||||
~MidiMessage ();
|
||||
|
||||
MidiMessage& operator= (const MidiMessage& message);
|
||||
MidiMessage& operator= (const std::vector<uchar>& bytes);
|
||||
MidiMessage& operator= (const std::vector<char>& bytes);
|
||||
MidiMessage& operator= (const std::vector<int>& bytes);
|
||||
|
||||
void sortTrack (void);
|
||||
void sortTrackWithSequence(void);
|
||||
|
||||
static std::vector<uchar> intToVlv (int value);
|
||||
static double frequencyToSemitones (double frequency, double a4frequency = 440.0);
|
||||
|
||||
// data access convenience functions (returns -1 if not present):
|
||||
int getP0 (void) const;
|
||||
int getP1 (void) const;
|
||||
int getP2 (void) const;
|
||||
int getP3 (void) const;
|
||||
void setP0 (int value);
|
||||
void setP1 (int value);
|
||||
void setP2 (int value);
|
||||
void setP3 (int value);
|
||||
|
||||
int getSize (void) const;
|
||||
void setSize (int asize);
|
||||
int setSizeToCommand (void);
|
||||
int resizeToCommand (void);
|
||||
|
||||
// note-message convenience functions:
|
||||
int getKeyNumber (void) const;
|
||||
int getVelocity (void) const;
|
||||
void setKeyNumber (int value);
|
||||
void setVelocity (int value);
|
||||
void setSpelling (int base7, int accidental);
|
||||
void getSpelling (int& base7, int& accidental);
|
||||
|
||||
// controller-message convenience functions:
|
||||
int getControllerNumber (void) const;
|
||||
int getControllerValue (void) const;
|
||||
|
||||
int getCommandNibble (void) const;
|
||||
int getCommandByte (void) const;
|
||||
int getChannelNibble (void) const;
|
||||
int getChannel (void) const;
|
||||
|
||||
void setCommandByte (int value);
|
||||
void setCommand (int value);
|
||||
void setCommand (int value, int p1);
|
||||
void setCommand (int value, int p1, int p2);
|
||||
void setCommandNibble (int value);
|
||||
void setChannelNibble (int value);
|
||||
void setChannel (int value);
|
||||
void setParameters (int p1, int p2);
|
||||
void setParameters (int p1);
|
||||
void setMessage (const std::vector<uchar>& message);
|
||||
void setMessage (const std::vector<char>& message);
|
||||
void setMessage (const std::vector<int>& message);
|
||||
|
||||
// message-type convenience functions:
|
||||
bool isMetaMessage (void) const;
|
||||
bool isMeta (void) const;
|
||||
bool isNote (void) const;
|
||||
bool isNoteOff (void) const;
|
||||
bool isNoteOn (void) const;
|
||||
bool isAftertouch (void) const;
|
||||
bool isController (void) const;
|
||||
bool isSustain (void) const; // controller 64
|
||||
bool isSustainOn (void) const;
|
||||
bool isSustainOff (void) const;
|
||||
bool isSoft (void) const; // controller 67
|
||||
bool isSoftOn (void) const;
|
||||
bool isSoftOff (void) const;
|
||||
bool isPatchChange (void) const;
|
||||
bool isTimbre (void) const;
|
||||
bool isPressure (void) const;
|
||||
bool isPitchbend (void) const;
|
||||
bool isEmpty (void) const; // see MidiFile::removeEmpties()
|
||||
|
||||
// helper functions to create various MidiMessages:
|
||||
void makeNoteOn (int channel, int key, int velocity);
|
||||
void makeNoteOff (int channel, int key, int velocity);
|
||||
void makeNoteOff (int channel, int key);
|
||||
void makeNoteOff (void);
|
||||
void makePatchChange (int channel, int patchnum);
|
||||
void makeTimbre (int channel, int patchnum);
|
||||
void makeController (int channel, int num, int value);
|
||||
void makePitchBend (int channel, int lsb, int msb);
|
||||
void makePitchBend (int channel, int value);
|
||||
void makePitchBendDouble (int channel, double value);
|
||||
void makePitchbend (int channel, int lsb, int msb) { makePitchBend(channel, lsb, msb); }
|
||||
void makePitchbend (int channel, int value) { makePitchBend(channel, value); }
|
||||
void makePitchbendDouble (int channel, double value) { makePitchBendDouble(channel, value); }
|
||||
|
||||
// helper functions to create various continuous controller messages:
|
||||
void makeSustain (int channel, int value);
|
||||
void makeSustainPedal (int channel, int value);
|
||||
void makeSustainOn (int channel);
|
||||
void makeSustainPedalOn (int channel);
|
||||
void makeSustainOff (int channel);
|
||||
void makeSustainPedalOff (int channel);
|
||||
|
||||
// meta-message creation and helper functions:
|
||||
void makeMetaMessage (int mnum, const std::string& data);
|
||||
void makeText (const std::string& name);
|
||||
void makeCopyright (const std::string& text);
|
||||
void makeTrackName (const std::string& name);
|
||||
void makeInstrumentName (const std::string& name);
|
||||
void makeLyric (const std::string& text);
|
||||
void makeMarker (const std::string& text);
|
||||
void makeCue (const std::string& text);
|
||||
void makeKeySignature (int fifths, bool mode = 0);
|
||||
void makeTimeSignature (int top, int bottom,
|
||||
int clocksPerClick = 24,
|
||||
int num32dsPerQuarter = 8);
|
||||
|
||||
void makeTempo (double tempo) { setTempo(tempo); }
|
||||
int getTempoMicro (void) const;
|
||||
int getTempoMicroseconds (void) const;
|
||||
double getTempoSeconds (void) const;
|
||||
double getTempoBPM (void) const;
|
||||
double getTempoTPS (int tpq) const;
|
||||
double getTempoSPT (int tpq) const;
|
||||
|
||||
int getMetaType (void) const;
|
||||
bool isText (void) const;
|
||||
bool isCopyright (void) const;
|
||||
bool isTrackName (void) const;
|
||||
bool isInstrumentName (void) const;
|
||||
bool isLyricText (void) const;
|
||||
bool isMarkerText (void) const;
|
||||
bool isTempo (void) const;
|
||||
bool isTimeSignature (void) const;
|
||||
bool isKeySignature (void) const;
|
||||
bool isEndOfTrack (void) const;
|
||||
|
||||
std::string getMetaContent (void) const;
|
||||
void setMetaContent (const std::string& content);
|
||||
void setTempo (double tempo);
|
||||
void setTempoMicroseconds (int microseconds);
|
||||
void setMetaTempo (double tempo);
|
||||
|
||||
|
||||
void makeSysExMessage (const std::vector<uchar>& data);
|
||||
|
||||
// helper functions to create MTS tunings by key (real-time sysex)
|
||||
|
||||
// MTS type 2: Real-time frequency assignment to a arbitrary list of MIDI key numbers.
|
||||
// See page 2 of: https://docs.google.com/viewer?url=https://www.midi.org/component/edocman/midi-tuning-updated/fdocument?Itemid=9999
|
||||
void makeMts2_KeyTuningByFrequency (int key, double frequency, int program = 0);
|
||||
void makeMts2_KeyTuningsByFrequency (int key, double frequency, int program = 0);
|
||||
void makeMts2_KeyTuningsByFrequency (std::vector<std::pair<int, double>>& mapping, int program = 0);
|
||||
void makeMts2_KeyTuningBySemitone (int key, double semitone, int program = 0);
|
||||
void makeMts2_KeyTuningsBySemitone (int key, double semitone, int program = 0);
|
||||
void makeMts2_KeyTuningsBySemitone (std::vector<std::pair<int, double>>& mapping, int program = 0);
|
||||
|
||||
// MTS type 9: Real-time octave temperaments by +/- 100 cents deviation from ET
|
||||
// See page 7 of: https://docs.google.com/viewer?url=https://www.midi.org/component/edocman/midi-tuning-updated/fdocument?Itemid=9999
|
||||
void makeMts9_TemperamentByCentsDeviationFromET (std::vector<double>& mapping, int referencePitchClass = 0, int channelMask = 0b1111111111111111);
|
||||
void makeTemperamentEqual(int referencePitchClass = 0, int channelMask = 0b1111111111111111);
|
||||
void makeTemperamentBad(double maxDeviationCents = 100.0, int referencePitchClass = 0, int channelMask = 0b1111111111111111);
|
||||
void makeTemperamentPythagorean(int referencePitchClass = 2, int channelMask = 0b1111111111111111);
|
||||
void makeTemperamentMeantone(double fraction = 0.25, int referencePitchClass = 2, int channelMask = 0b1111111111111111);
|
||||
void makeTemperamentMeantoneCommaQuarter(int referencePitchClass = 2, int channelMask = 0b1111111111111111);
|
||||
void makeTemperamentMeantoneCommaThird(int referencePitchClass = 2, int channelMask = 0b1111111111111111);
|
||||
void makeTemperamentMeantoneCommaHalf(int referencePitchClass = 2, int channelMask = 0b1111111111111111);
|
||||
|
||||
};
|
||||
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, MidiMessage& event);
|
||||
|
||||
|
||||
} // end of namespace smf
|
||||
|
||||
|
||||
#endif /* _MIDIMESSAGE_H_INCLUDED */
|
||||
|
||||
|
||||
|
|
@ -48,10 +48,7 @@
|
|||
|
||||
void Aircraft::UpdateDeltaXY()
|
||||
{
|
||||
this->x_offs = -1;
|
||||
this->y_offs = -1;
|
||||
this->x_extent = 2;
|
||||
this->y_extent = 2;
|
||||
this->bounds = {{-1, -1, 0}, {2, 2, 0}, {}};
|
||||
|
||||
switch (this->subtype) {
|
||||
default: NOT_REACHED();
|
||||
|
@ -64,21 +61,21 @@ void Aircraft::UpdateDeltaXY()
|
|||
case LANDING:
|
||||
case HELILANDING:
|
||||
case FLYING:
|
||||
this->x_extent = 24;
|
||||
this->y_extent = 24;
|
||||
/* Bounds are not centred on the aircraft. */
|
||||
this->bounds.extent.x = 24;
|
||||
this->bounds.extent.y = 24;
|
||||
break;
|
||||
}
|
||||
this->z_extent = 5;
|
||||
this->bounds.extent.z = 5;
|
||||
break;
|
||||
|
||||
case AIR_SHADOW:
|
||||
this->z_extent = 1;
|
||||
this->x_offs = 0;
|
||||
this->y_offs = 0;
|
||||
this->bounds.extent.z = 1;
|
||||
this->bounds.origin = {};
|
||||
break;
|
||||
|
||||
case AIR_ROTOR:
|
||||
this->z_extent = 1;
|
||||
this->bounds.extent.z = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,36 +69,45 @@ static void DrawClearLandFence(const TileInfo *ti)
|
|||
/* combine fences into one sprite object */
|
||||
StartSpriteCombine();
|
||||
|
||||
int maxz = GetSlopeMaxPixelZ(ti->tileh);
|
||||
SpriteBounds bounds{{}, {TILE_SIZE, TILE_SIZE, 4}, {}};
|
||||
|
||||
bounds.extent.z += GetSlopeMaxPixelZ(ti->tileh);
|
||||
|
||||
uint fence_nw = GetFence(ti->tile, DIAGDIR_NW);
|
||||
if (fence_nw != 0) {
|
||||
int z = GetSlopePixelZInCorner(ti->tileh, CORNER_W);
|
||||
bounds.offset.x = 0;
|
||||
bounds.offset.y = -static_cast<int>(TILE_SIZE);
|
||||
bounds.offset.z = GetSlopePixelZInCorner(ti->tileh, CORNER_W);
|
||||
SpriteID sprite = _clear_land_fence_sprites[fence_nw - 1] + _fence_mod_by_tileh_nw[ti->tileh];
|
||||
AddSortableSpriteToDraw(sprite, PAL_NONE, ti->x, ti->y - 16, 16, 32, maxz - z + 4, ti->z + z, false, 0, 16, -z);
|
||||
AddSortableSpriteToDraw(sprite, PAL_NONE, *ti, bounds, false);
|
||||
}
|
||||
|
||||
uint fence_ne = GetFence(ti->tile, DIAGDIR_NE);
|
||||
if (fence_ne != 0) {
|
||||
int z = GetSlopePixelZInCorner(ti->tileh, CORNER_E);
|
||||
bounds.offset.x = -static_cast<int>(TILE_SIZE);
|
||||
bounds.offset.y = 0;
|
||||
bounds.offset.z = GetSlopePixelZInCorner(ti->tileh, CORNER_E);
|
||||
SpriteID sprite = _clear_land_fence_sprites[fence_ne - 1] + _fence_mod_by_tileh_ne[ti->tileh];
|
||||
AddSortableSpriteToDraw(sprite, PAL_NONE, ti->x - 16, ti->y, 32, 16, maxz - z + 4, ti->z + z, false, 16, 0, -z);
|
||||
AddSortableSpriteToDraw(sprite, PAL_NONE, *ti, bounds, false);
|
||||
}
|
||||
|
||||
uint fence_sw = GetFence(ti->tile, DIAGDIR_SW);
|
||||
uint fence_se = GetFence(ti->tile, DIAGDIR_SE);
|
||||
|
||||
if (fence_sw != 0 || fence_se != 0) {
|
||||
int z = GetSlopePixelZInCorner(ti->tileh, CORNER_S);
|
||||
bounds.offset.x = 0;
|
||||
bounds.offset.y = 0;
|
||||
bounds.offset.z = GetSlopePixelZInCorner(ti->tileh, CORNER_S);
|
||||
|
||||
if (fence_sw != 0) {
|
||||
SpriteID sprite = _clear_land_fence_sprites[fence_sw - 1] + _fence_mod_by_tileh_sw[ti->tileh];
|
||||
AddSortableSpriteToDraw(sprite, PAL_NONE, ti->x, ti->y, 16, 16, maxz - z + 4, ti->z + z, false, 0, 0, -z);
|
||||
AddSortableSpriteToDraw(sprite, PAL_NONE, *ti, bounds, false);
|
||||
}
|
||||
|
||||
if (fence_se != 0) {
|
||||
SpriteID sprite = _clear_land_fence_sprites[fence_se - 1] + _fence_mod_by_tileh_se[ti->tileh];
|
||||
AddSortableSpriteToDraw(sprite, PAL_NONE, ti->x, ti->y, 16, 16, maxz - z + 4, ti->z + z, false, 0, 0, -z);
|
||||
AddSortableSpriteToDraw(sprite, PAL_NONE, *ti, bounds, false);
|
||||
|
||||
}
|
||||
}
|
||||
EndSpriteCombine();
|
||||
|
|
|
@ -28,15 +28,30 @@ inline int CentreBounds(int min, int max, int size)
|
|||
return (min + max - size + 1) / 2;
|
||||
}
|
||||
|
||||
/** Coordinates of a point in 2D */
|
||||
struct Point {
|
||||
int x;
|
||||
int y;
|
||||
/** A coordinate with two dimensons. */
|
||||
template <typename T>
|
||||
struct Coord2D {
|
||||
T x = 0; ///< X coordinate.
|
||||
T y = 0; ///< Y coordinate.
|
||||
|
||||
constexpr Point() : x(0), y(0) {}
|
||||
constexpr Point(int x, int y) : x(x), y(y) {}
|
||||
constexpr Coord2D() = default;
|
||||
constexpr Coord2D(T x, T y) : x(x), y(y) {}
|
||||
};
|
||||
|
||||
/** A coordinate with three dimensions. */
|
||||
template <typename T>
|
||||
struct Coord3D {
|
||||
T x = 0; ///< X coordinate.
|
||||
T y = 0; ///< Y coordinate.
|
||||
T z = 0; ///< Z coordinate.
|
||||
|
||||
constexpr Coord3D() = default;
|
||||
constexpr Coord3D(T x, T y, T z) : x(x), y(y), z(z) {}
|
||||
};
|
||||
|
||||
/** Coordinates of a point in 2D */
|
||||
using Point = Coord2D<int>;
|
||||
|
||||
/** Dimensions (a width and height) of a rectangle in 2D */
|
||||
struct Dimension {
|
||||
uint width;
|
||||
|
|
|
@ -992,9 +992,5 @@ void ReleaseDisasterVehicle(VehicleID vehicle)
|
|||
|
||||
void DisasterVehicle::UpdateDeltaXY()
|
||||
{
|
||||
this->x_offs = -1;
|
||||
this->y_offs = -1;
|
||||
this->x_extent = 2;
|
||||
this->y_extent = 2;
|
||||
this->z_extent = 5;
|
||||
this->bounds = {{-1, -1, 0}, {2, 2, 5}, {}};
|
||||
}
|
||||
|
|
|
@ -619,11 +619,7 @@ bool EffectVehicle::Tick()
|
|||
|
||||
void EffectVehicle::UpdateDeltaXY()
|
||||
{
|
||||
this->x_offs = 0;
|
||||
this->y_offs = 0;
|
||||
this->x_extent = 1;
|
||||
this->y_extent = 1;
|
||||
this->z_extent = 1;
|
||||
this->bounds = {{}, {1, 1, 1}, {}};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -248,27 +248,12 @@ static int GetPCPElevation(TileIndex tile, DiagDirection pcp_pos)
|
|||
*/
|
||||
void DrawRailCatenaryOnTunnel(const TileInfo *ti)
|
||||
{
|
||||
/* xmin, ymin, xmax + 1, ymax + 1 of BB */
|
||||
static const int tunnel_wire_bb[4][4] = {
|
||||
{ 0, 1, 16, 15 }, // NE
|
||||
{ 1, 0, 15, 16 }, // SE
|
||||
{ 0, 1, 16, 15 }, // SW
|
||||
{ 1, 0, 15, 16 }, // NW
|
||||
};
|
||||
|
||||
DiagDirection dir = GetTunnelBridgeDirection(ti->tile);
|
||||
|
||||
SpriteID wire_base = GetWireBase(ti->tile);
|
||||
|
||||
const SortableSpriteStruct *sss = &_rail_catenary_sprite_data_tunnel[dir];
|
||||
const int *bb_data = tunnel_wire_bb[dir];
|
||||
AddSortableSpriteToDraw(
|
||||
wire_base + sss->image_offset, PAL_NONE, ti->x + sss->x_offset, ti->y + sss->y_offset,
|
||||
bb_data[2] - sss->x_offset, bb_data[3] - sss->y_offset, BB_Z_SEPARATOR - sss->z_offset + 1,
|
||||
GetTilePixelZ(ti->tile) + sss->z_offset,
|
||||
IsTransparencySet(TO_CATENARY),
|
||||
bb_data[0] - sss->x_offset, bb_data[1] - sss->y_offset, BB_Z_SEPARATOR - sss->z_offset
|
||||
);
|
||||
const SortableSpriteStruct &sss = _rail_catenary_sprite_data_tunnel[dir];
|
||||
AddSortableSpriteToDraw(wire_base + sss.image_offset, PAL_NONE, ti->x, ti->y, GetTilePixelZ(ti->tile), sss, IsTransparencySet(TO_CATENARY));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -440,8 +425,8 @@ static void DrawRailCatenaryRailway(const TileInfo *ti)
|
|||
continue; // No neighbour, go looking for a better position
|
||||
}
|
||||
|
||||
AddSortableSpriteToDraw(pylon_base + _pylon_sprites[temp], PAL_NONE, x, y, 1, 1, BB_HEIGHT_UNDER_BRIDGE,
|
||||
elevation, IsTransparencySet(TO_CATENARY), -1, -1);
|
||||
AddSortableSpriteToDraw(pylon_base + _pylon_sprites[temp], PAL_NONE, x, y, elevation,
|
||||
{{-1, -1, 0}, {1, 1, BB_HEIGHT_UNDER_BRIDGE}, {1, 1, 0}}, IsTransparencySet(TO_CATENARY));
|
||||
|
||||
break; // We already have drawn a pylon, bail out
|
||||
}
|
||||
|
@ -482,7 +467,7 @@ static void DrawRailCatenaryRailway(const TileInfo *ti)
|
|||
|
||||
assert(pcp_config != 0); // We have a pylon on neither end of the wire, that doesn't work (since we have no sprites for that)
|
||||
assert(!IsSteepSlope(tileh[TS_HOME]));
|
||||
const SortableSpriteStruct *sss = &_rail_catenary_sprite_data[_rail_wires[tileh_selector][t][pcp_config]];
|
||||
const SortableSpriteStruct &sss = _rail_catenary_sprite_data[_rail_wires[tileh_selector][t][pcp_config]];
|
||||
|
||||
/*
|
||||
* The "wire"-sprite position is inside the tile, i.e. 0 <= sss->?_offset < TILE_SIZE.
|
||||
|
@ -490,9 +475,8 @@ static void DrawRailCatenaryRailway(const TileInfo *ti)
|
|||
* Also note that the result of GetSlopePixelZ() is very special for bridge-ramps, so we round the result up or
|
||||
* down to the nearest full height change.
|
||||
*/
|
||||
AddSortableSpriteToDraw(wire_base + sss->image_offset, PAL_NONE, ti->x + sss->x_offset, ti->y + sss->y_offset,
|
||||
sss->x_size, sss->y_size, sss->z_size, (GetSlopePixelZ(ti->x + sss->x_offset, ti->y + sss->y_offset, true) + 4) / 8 * 8 + sss->z_offset,
|
||||
IsTransparencySet(TO_CATENARY));
|
||||
int z = (GetSlopePixelZ(ti->x + sss.origin.x, ti->y + sss.origin.y, true) + 4) / 8 * 8;
|
||||
AddSortableSpriteToDraw(wire_base + sss.image_offset, PAL_NONE, ti->x, ti->y, z, sss, IsTransparencySet(TO_CATENARY));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -530,13 +514,12 @@ void DrawRailCatenaryOnBridge(const TileInfo *ti)
|
|||
|
||||
SpriteID wire_base = GetWireBase(end, TCX_ON_BRIDGE);
|
||||
|
||||
AddSortableSpriteToDraw(wire_base + sss->image_offset, PAL_NONE, ti->x + sss->x_offset, ti->y + sss->y_offset,
|
||||
sss->x_size, sss->y_size, sss->z_size, height + sss->z_offset,
|
||||
IsTransparencySet(TO_CATENARY)
|
||||
);
|
||||
AddSortableSpriteToDraw(wire_base + sss->image_offset, PAL_NONE, ti->x, ti->y, height, *sss, IsTransparencySet(TO_CATENARY));
|
||||
|
||||
SpriteID pylon_base = GetPylonBase(end, TCX_ON_BRIDGE);
|
||||
|
||||
static constexpr SpriteBounds pylon_bounds{{-1, -1, 0}, {1, 1, BB_HEIGHT_UNDER_BRIDGE}, {1, 1, 0}};
|
||||
|
||||
/* Finished with wires, draw pylons
|
||||
* every other tile needs a pylon on the northern end */
|
||||
if (num % 2) {
|
||||
|
@ -545,7 +528,7 @@ void DrawRailCatenaryOnBridge(const TileInfo *ti)
|
|||
if (HasBit(tlg, (axis == AXIS_X ? 0 : 1))) ppp_pos = ReverseDir(ppp_pos);
|
||||
uint x = ti->x + _x_pcp_offsets[pcp_pos] + _x_ppp_offsets[ppp_pos];
|
||||
uint y = ti->y + _y_pcp_offsets[pcp_pos] + _y_ppp_offsets[ppp_pos];
|
||||
AddSortableSpriteToDraw(pylon_base + _pylon_sprites[ppp_pos], PAL_NONE, x, y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, height, IsTransparencySet(TO_CATENARY), -1, -1);
|
||||
AddSortableSpriteToDraw(pylon_base + _pylon_sprites[ppp_pos], PAL_NONE, x, y, height, pylon_bounds, IsTransparencySet(TO_CATENARY));
|
||||
}
|
||||
|
||||
/* need a pylon on the southern end of the bridge */
|
||||
|
@ -555,7 +538,7 @@ void DrawRailCatenaryOnBridge(const TileInfo *ti)
|
|||
if (HasBit(tlg, (axis == AXIS_X ? 0 : 1))) ppp_pos = ReverseDir(ppp_pos);
|
||||
uint x = ti->x + _x_pcp_offsets[pcp_pos] + _x_ppp_offsets[ppp_pos];
|
||||
uint y = ti->y + _y_pcp_offsets[pcp_pos] + _y_ppp_offsets[ppp_pos];
|
||||
AddSortableSpriteToDraw(pylon_base + _pylon_sprites[ppp_pos], PAL_NONE, x, y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, height, IsTransparencySet(TO_CATENARY), -1, -1);
|
||||
AddSortableSpriteToDraw(pylon_base + _pylon_sprites[ppp_pos], PAL_NONE, x, y, height, pylon_bounds, IsTransparencySet(TO_CATENARY));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -569,17 +552,12 @@ void DrawRailCatenary(const TileInfo *ti)
|
|||
switch (GetTileType(ti->tile)) {
|
||||
case MP_RAILWAY:
|
||||
if (IsRailDepot(ti->tile)) {
|
||||
const SortableSpriteStruct *sss = &_rail_catenary_sprite_data_depot[GetRailDepotDirection(ti->tile)];
|
||||
const SortableSpriteStruct &sss = _rail_catenary_sprite_data_depot[GetRailDepotDirection(ti->tile)];
|
||||
|
||||
SpriteID wire_base = GetWireBase(ti->tile);
|
||||
|
||||
/* This wire is not visible with the default depot sprites */
|
||||
AddSortableSpriteToDraw(
|
||||
wire_base + sss->image_offset, PAL_NONE, ti->x + sss->x_offset, ti->y + sss->y_offset,
|
||||
sss->x_size, sss->y_size, sss->z_size,
|
||||
GetTileMaxPixelZ(ti->tile) + sss->z_offset,
|
||||
IsTransparencySet(TO_CATENARY)
|
||||
);
|
||||
AddSortableSpriteToDraw(wire_base + sss.image_offset, PAL_NONE, ti->x, ti->y, GetTileMaxPixelZ(ti->tile), sss, IsTransparencySet(TO_CATENARY));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -374,13 +374,7 @@ static void DrawTile_Industry(TileInfo *ti)
|
|||
image = dits->building.sprite;
|
||||
if (image != 0) {
|
||||
AddSortableSpriteToDraw(image, SpriteLayoutPaletteTransform(image, dits->building.pal, GetColourPalette(ind->random_colour)),
|
||||
ti->x + dits->subtile_x,
|
||||
ti->y + dits->subtile_y,
|
||||
dits->width,
|
||||
dits->height,
|
||||
dits->dz,
|
||||
ti->z,
|
||||
IsTransparencySet(TO_INDUSTRIES));
|
||||
*ti, *dits, IsTransparencySet(TO_INDUSTRIES));
|
||||
|
||||
if (IsTransparencySet(TO_INDUSTRIES)) return;
|
||||
}
|
||||
|
|
|
@ -450,9 +450,8 @@ void DrawFoundation(TileInfo *ti, Foundation f)
|
|||
if (IsSteepSlope(ti->tileh)) {
|
||||
if (!IsNonContinuousFoundation(f)) {
|
||||
/* Lower part of foundation */
|
||||
AddSortableSpriteToDraw(
|
||||
leveled_base + (ti->tileh & ~SLOPE_STEEP), PAL_NONE, ti->x, ti->y, TILE_SIZE, TILE_SIZE, TILE_HEIGHT - 1, ti->z
|
||||
);
|
||||
static constexpr SpriteBounds bounds{{}, {TILE_SIZE, TILE_SIZE, TILE_HEIGHT - 1}, {}};
|
||||
AddSortableSpriteToDraw(leveled_base + (ti->tileh & ~SLOPE_STEEP), PAL_NONE, *ti, bounds);
|
||||
}
|
||||
|
||||
Corner highest_corner = GetHighestSlopeCorner(ti->tileh);
|
||||
|
@ -462,24 +461,25 @@ void DrawFoundation(TileInfo *ti, Foundation f)
|
|||
/* inclined foundation */
|
||||
uint8_t inclined = highest_corner * 2 + (f == FOUNDATION_INCLINED_Y ? 1 : 0);
|
||||
|
||||
AddSortableSpriteToDraw(inclined_base + inclined, PAL_NONE, ti->x, ti->y,
|
||||
f == FOUNDATION_INCLINED_X ? TILE_SIZE : 1,
|
||||
f == FOUNDATION_INCLINED_Y ? TILE_SIZE : 1,
|
||||
TILE_HEIGHT, ti->z
|
||||
);
|
||||
SpriteBounds bounds{{}, {1, 1, TILE_HEIGHT}, {}};
|
||||
if (f == FOUNDATION_INCLINED_X) bounds.extent.x = TILE_SIZE;
|
||||
if (f == FOUNDATION_INCLINED_Y) bounds.extent.y = TILE_SIZE;
|
||||
AddSortableSpriteToDraw(inclined_base + inclined, PAL_NONE, *ti, bounds);
|
||||
OffsetGroundSprite(0, 0);
|
||||
} else if (IsLeveledFoundation(f)) {
|
||||
AddSortableSpriteToDraw(leveled_base + SlopeWithOneCornerRaised(highest_corner), PAL_NONE, ti->x, ti->y, TILE_SIZE, TILE_SIZE, TILE_HEIGHT - 1, ti->z - TILE_HEIGHT);
|
||||
static constexpr SpriteBounds bounds{{0, 0, -(int)TILE_HEIGHT}, {TILE_SIZE, TILE_SIZE, TILE_HEIGHT - 1}, {}};
|
||||
AddSortableSpriteToDraw(leveled_base + SlopeWithOneCornerRaised(highest_corner), PAL_NONE, *ti, bounds);
|
||||
OffsetGroundSprite(0, -(int)TILE_HEIGHT);
|
||||
} else if (f == FOUNDATION_STEEP_LOWER) {
|
||||
/* one corner raised */
|
||||
OffsetGroundSprite(0, -(int)TILE_HEIGHT);
|
||||
} else {
|
||||
/* halftile foundation */
|
||||
int x_bb = (((highest_corner == CORNER_W) || (highest_corner == CORNER_S)) ? TILE_SIZE / 2 : 0);
|
||||
int y_bb = (((highest_corner == CORNER_S) || (highest_corner == CORNER_E)) ? TILE_SIZE / 2 : 0);
|
||||
int8_t x_bb = (((highest_corner == CORNER_W) || (highest_corner == CORNER_S)) ? TILE_SIZE / 2 : 0);
|
||||
int8_t y_bb = (((highest_corner == CORNER_S) || (highest_corner == CORNER_E)) ? TILE_SIZE / 2 : 0);
|
||||
|
||||
AddSortableSpriteToDraw(halftile_base + highest_corner, PAL_NONE, ti->x + x_bb, ti->y + y_bb, TILE_SIZE / 2, TILE_SIZE / 2, TILE_HEIGHT - 1, ti->z + TILE_HEIGHT);
|
||||
SpriteBounds bounds{{x_bb, y_bb, TILE_HEIGHT}, {TILE_SIZE / 2, TILE_SIZE / 2, TILE_HEIGHT - 1}, {}};
|
||||
AddSortableSpriteToDraw(halftile_base + highest_corner, PAL_NONE, *ti, bounds);
|
||||
/* Reposition ground sprite back to original position after bounding box change above. This is similar to
|
||||
* RemapCoords() but without zoom scaling. */
|
||||
Point pt = {(y_bb - x_bb) * 2, y_bb + x_bb};
|
||||
|
@ -488,15 +488,17 @@ void DrawFoundation(TileInfo *ti, Foundation f)
|
|||
} else {
|
||||
if (IsLeveledFoundation(f)) {
|
||||
/* leveled foundation */
|
||||
AddSortableSpriteToDraw(leveled_base + ti->tileh, PAL_NONE, ti->x, ti->y, TILE_SIZE, TILE_SIZE, TILE_HEIGHT - 1, ti->z);
|
||||
static constexpr SpriteBounds bounds{{}, {TILE_SIZE, TILE_SIZE, TILE_HEIGHT - 1}, {}};
|
||||
AddSortableSpriteToDraw(leveled_base + ti->tileh, PAL_NONE, *ti, bounds);
|
||||
OffsetGroundSprite(0, -(int)TILE_HEIGHT);
|
||||
} else if (IsNonContinuousFoundation(f)) {
|
||||
/* halftile foundation */
|
||||
Corner halftile_corner = GetHalftileFoundationCorner(f);
|
||||
int x_bb = (((halftile_corner == CORNER_W) || (halftile_corner == CORNER_S)) ? TILE_SIZE / 2 : 0);
|
||||
int y_bb = (((halftile_corner == CORNER_S) || (halftile_corner == CORNER_E)) ? TILE_SIZE / 2 : 0);
|
||||
int8_t x_bb = (((halftile_corner == CORNER_W) || (halftile_corner == CORNER_S)) ? TILE_SIZE / 2 : 0);
|
||||
int8_t y_bb = (((halftile_corner == CORNER_S) || (halftile_corner == CORNER_E)) ? TILE_SIZE / 2 : 0);
|
||||
|
||||
AddSortableSpriteToDraw(halftile_base + halftile_corner, PAL_NONE, ti->x + x_bb, ti->y + y_bb, TILE_SIZE / 2, TILE_SIZE / 2, TILE_HEIGHT - 1, ti->z);
|
||||
SpriteBounds bounds{{x_bb, y_bb, 0}, {TILE_SIZE / 2, TILE_SIZE / 2, TILE_HEIGHT - 1}, {}};
|
||||
AddSortableSpriteToDraw(halftile_base + halftile_corner, PAL_NONE, *ti, bounds);
|
||||
/* Reposition ground sprite back to original position after bounding box change above. This is similar to
|
||||
* RemapCoords() but without zoom scaling. */
|
||||
Point pt = {(y_bb - x_bb) * 2, y_bb + x_bb};
|
||||
|
@ -511,17 +513,17 @@ void DrawFoundation(TileInfo *ti, Foundation f)
|
|||
/* tile-slope = sloped along X/Y, foundation-slope = three corners raised */
|
||||
spr = inclined_base + 2 * GetRailFoundationCorner(f) + ((ti->tileh == SLOPE_SW || ti->tileh == SLOPE_NE) ? 1 : 0);
|
||||
}
|
||||
AddSortableSpriteToDraw(spr, PAL_NONE, ti->x, ti->y, TILE_SIZE, TILE_SIZE, TILE_HEIGHT - 1, ti->z);
|
||||
static constexpr SpriteBounds bounds{{}, {TILE_SIZE, TILE_SIZE, TILE_HEIGHT - 1}, {}};
|
||||
AddSortableSpriteToDraw(spr, PAL_NONE, *ti, bounds);
|
||||
OffsetGroundSprite(0, 0);
|
||||
} else {
|
||||
/* inclined foundation */
|
||||
uint8_t inclined = GetHighestSlopeCorner(ti->tileh) * 2 + (f == FOUNDATION_INCLINED_Y ? 1 : 0);
|
||||
|
||||
AddSortableSpriteToDraw(inclined_base + inclined, PAL_NONE, ti->x, ti->y,
|
||||
f == FOUNDATION_INCLINED_X ? TILE_SIZE : 1,
|
||||
f == FOUNDATION_INCLINED_Y ? TILE_SIZE : 1,
|
||||
TILE_HEIGHT, ti->z
|
||||
);
|
||||
SpriteBounds bounds{{}, {1, 1, TILE_HEIGHT}, {}};
|
||||
if (f == FOUNDATION_INCLINED_X) bounds.extent.x = TILE_SIZE;
|
||||
if (f == FOUNDATION_INCLINED_Y) bounds.extent.y = TILE_SIZE;
|
||||
AddSortableSpriteToDraw(inclined_base + inclined, PAL_NONE, *ti, bounds);
|
||||
OffsetGroundSprite(0, 0);
|
||||
}
|
||||
ti->z += ApplyPixelFoundationToSlope(f, ti->tileh);
|
||||
|
|
|
@ -635,8 +635,11 @@ STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL :{BLACK}Não mos
|
|||
STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO :{BLACK}Mostrar/Ocultar gráfico para este tipo de carga
|
||||
STR_GRAPH_CARGO_PAYMENT_CARGO :{TINY_FONT}{BLACK}{STRING}
|
||||
|
||||
STR_GRAPH_INDUSTRY_CAPTION :{WHITE}{INDUSTRY} - Histórico de Carga
|
||||
STR_GRAPH_INDUSTRY_RANGE_PRODUCED :Produzido
|
||||
STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED :Transportado
|
||||
STR_GRAPH_INDUSTRY_RANGE_DELIVERED :Entregue
|
||||
STR_GRAPH_INDUSTRY_RANGE_WAITING :Aguardando
|
||||
|
||||
STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP :{BLACK}Mostrar avaliações detalhadas de desempenho
|
||||
|
||||
|
@ -4024,6 +4027,8 @@ STR_INDUSTRY_VIEW_PRODUCTION_LAST_MONTH_TITLE :{BLACK}Produç
|
|||
STR_INDUSTRY_VIEW_PRODUCTION_LAST_MINUTE_TITLE :{BLACK}Produção no último minuto:
|
||||
STR_INDUSTRY_VIEW_TRANSPORTED :{YELLOW}{CARGO_LONG}{STRING}{BLACK} ({COMMA}% transportado)
|
||||
STR_INDUSTRY_VIEW_LOCATION_TOOLTIP :{BLACK}Centralizar visualização principal na localização da indústria. Ctrl+Clique para abrir uma nova visualização na localização da indústria
|
||||
STR_INDUSTRY_VIEW_CARGO_GRAPH :{BLACK}Gráfico da Carga
|
||||
STR_INDUSTRY_VIEW_CARGO_GRAPH_TOOLTIP :{BLACK}Mostrar o gráfico do histórico de carga da indústria
|
||||
STR_INDUSTRY_VIEW_PRODUCTION_LEVEL :{BLACK}Nível de produção: {YELLOW}{COMMA}%
|
||||
STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE :{YELLOW}A indústria anunciou fechamento iminente!
|
||||
|
||||
|
|
|
@ -634,8 +634,11 @@ STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL :{BLACK}Display
|
|||
STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO :{BLACK}Toggle graph of this cargo type
|
||||
STR_GRAPH_CARGO_PAYMENT_CARGO :{TINY_FONT}{BLACK}{STRING}
|
||||
|
||||
STR_GRAPH_INDUSTRY_CAPTION :{WHITE}{INDUSTRY} - Cargo History
|
||||
STR_GRAPH_INDUSTRY_RANGE_PRODUCED :Produced
|
||||
STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED :Transported
|
||||
STR_GRAPH_INDUSTRY_RANGE_DELIVERED :Delivered
|
||||
STR_GRAPH_INDUSTRY_RANGE_WAITING :Waiting
|
||||
|
||||
STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP :{BLACK}Show detailed performance ratings
|
||||
|
||||
|
@ -4023,6 +4026,8 @@ STR_INDUSTRY_VIEW_PRODUCTION_LAST_MONTH_TITLE :{BLACK}Producti
|
|||
STR_INDUSTRY_VIEW_PRODUCTION_LAST_MINUTE_TITLE :{BLACK}Production last minute:
|
||||
STR_INDUSTRY_VIEW_TRANSPORTED :{YELLOW}{CARGO_LONG}{STRING}{BLACK} ({COMMA}% transported)
|
||||
STR_INDUSTRY_VIEW_LOCATION_TOOLTIP :{BLACK}Centre the main view on industry location. Ctrl+Click to open a new viewport on industry location
|
||||
STR_INDUSTRY_VIEW_CARGO_GRAPH :{BLACK}Cargo Graph
|
||||
STR_INDUSTRY_VIEW_CARGO_GRAPH_TOOLTIP :{BLACK}Shows the graph of industry cargo history
|
||||
STR_INDUSTRY_VIEW_PRODUCTION_LEVEL :{BLACK}Production level: {YELLOW}{COMMA}%
|
||||
STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE :{YELLOW}The industry has announced imminent closure!
|
||||
|
||||
|
|
|
@ -634,8 +634,11 @@ STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL :{BLACK}Älä n
|
|||
STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO :{BLACK}Valitse, näytetäänkö tämän rahdin kuvaaja
|
||||
STR_GRAPH_CARGO_PAYMENT_CARGO :{TINY_FONT}{BLACK}{STRING}
|
||||
|
||||
STR_GRAPH_INDUSTRY_CAPTION :{WHITE}{INDUSTRY} – rahtihistoria
|
||||
STR_GRAPH_INDUSTRY_RANGE_PRODUCED :Tuotettu
|
||||
STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED :Kuljetettu
|
||||
STR_GRAPH_INDUSTRY_RANGE_DELIVERED :Toimitettu
|
||||
STR_GRAPH_INDUSTRY_RANGE_WAITING :Odottava
|
||||
|
||||
STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP :{BLACK}Näytä tarkat suorituskykyarviot
|
||||
|
||||
|
@ -4023,6 +4026,8 @@ STR_INDUSTRY_VIEW_PRODUCTION_LAST_MONTH_TITLE :{BLACK}Tuotanto
|
|||
STR_INDUSTRY_VIEW_PRODUCTION_LAST_MINUTE_TITLE :{BLACK}Tuotanto viime minuutissa:
|
||||
STR_INDUSTRY_VIEW_TRANSPORTED :{YELLOW}{CARGO_LONG}{STRING}{BLACK} ({COMMA}{NBSP}% kuljetettu)
|
||||
STR_INDUSTRY_VIEW_LOCATION_TOOLTIP :{BLACK}Keskitä päänäkymä tuotantolaitoksen sijaintiin. Ctrl+napsautus avataksesi uuden näkymäikkunan laitoksen sijaintiin
|
||||
STR_INDUSTRY_VIEW_CARGO_GRAPH :{BLACK}Rahdin kuvaaja
|
||||
STR_INDUSTRY_VIEW_CARGO_GRAPH_TOOLTIP :{BLACK}Näyttää tuotantolaitoksen rahtihistorian kuvaajana
|
||||
STR_INDUSTRY_VIEW_PRODUCTION_LEVEL :{BLACK}Tuotantotaso: {YELLOW}{COMMA}{NBSP}%
|
||||
STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE :{YELLOW}Teollisuuslaitos ilmoittaa välittömästä lakkautuksesta!
|
||||
|
||||
|
|
|
@ -727,8 +727,11 @@ STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL :{BLACK}Εμφά
|
|||
STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO :{BLACK}Εναλλαγή γραφήματος αυτού του τύπου φορτίου
|
||||
STR_GRAPH_CARGO_PAYMENT_CARGO :{TINY_FONT}{BLACK}{STRING}
|
||||
|
||||
STR_GRAPH_INDUSTRY_CAPTION :{WHITE}{INDUSTRY} - Ιστορικό φορτίου
|
||||
STR_GRAPH_INDUSTRY_RANGE_PRODUCED :Παράχθηκε/αν
|
||||
STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED :Μεταφέρθηκε/αν
|
||||
STR_GRAPH_INDUSTRY_RANGE_DELIVERED :Παραδόθηκε
|
||||
STR_GRAPH_INDUSTRY_RANGE_WAITING :Αναμονή
|
||||
|
||||
STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP :{BLACK}Εμφάνιση λεπτομεριών αποδόσεων
|
||||
|
||||
|
@ -4117,6 +4120,8 @@ STR_INDUSTRY_VIEW_PRODUCTION_LAST_MONTH_TITLE :{BLACK}Παρα
|
|||
STR_INDUSTRY_VIEW_PRODUCTION_LAST_MINUTE_TITLE :{BLACK}Παραγωγή τελευταίου λεπτού:
|
||||
STR_INDUSTRY_VIEW_TRANSPORTED :{YELLOW}{CARGO_LONG}{STRING}{BLACK} ({COMMA}% μεταφέρθηκαν)
|
||||
STR_INDUSTRY_VIEW_LOCATION_TOOLTIP :{BLACK}Κεντράρισμα εικόνας στην περιοχή της βιομηχανίας. Ctrl+Κλικ για άνοιγμα νέου παραθύρου προβολής στην περιοχή της βιομηχανίας
|
||||
STR_INDUSTRY_VIEW_CARGO_GRAPH :{BLACK}Γράφημα φορτίου
|
||||
STR_INDUSTRY_VIEW_CARGO_GRAPH_TOOLTIP :{BLACK}Εμφανίζει το γράφημα του ιστορικού του φορτίου της βιομηχανίας
|
||||
STR_INDUSTRY_VIEW_PRODUCTION_LEVEL :{BLACK}Επίπεδο παραγωγής: {YELLOW}{COMMA}%
|
||||
STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE :{YELLOW}Η βιομηχανία έχει ανακοινώσει άμεσο κλείσιμο!
|
||||
|
||||
|
|
|
@ -635,8 +635,11 @@ STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL :{BLACK}화물
|
|||
STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO :{BLACK}이 화물의 그래프를 표시하거나 숨깁니다
|
||||
STR_GRAPH_CARGO_PAYMENT_CARGO :{TINY_FONT}{BLACK}{STRING}
|
||||
|
||||
STR_GRAPH_INDUSTRY_CAPTION :{WHITE}{INDUSTRY} - 화물 그래프
|
||||
STR_GRAPH_INDUSTRY_RANGE_PRODUCED :생산량
|
||||
STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED :수송량
|
||||
STR_GRAPH_INDUSTRY_RANGE_DELIVERED :수송됨
|
||||
STR_GRAPH_INDUSTRY_RANGE_WAITING :대기 중
|
||||
|
||||
STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP :{BLACK}상세 성취도를 봅니다.
|
||||
|
||||
|
@ -4024,6 +4027,8 @@ STR_INDUSTRY_VIEW_PRODUCTION_LAST_MONTH_TITLE :{BLACK}지난
|
|||
STR_INDUSTRY_VIEW_PRODUCTION_LAST_MINUTE_TITLE :{BLACK}지난 1분간 생산량:
|
||||
STR_INDUSTRY_VIEW_TRANSPORTED :{YELLOW}{CARGO_LONG}{STRING}{BLACK} ({COMMA}% 수송됨)
|
||||
STR_INDUSTRY_VIEW_LOCATION_TOOLTIP :{BLACK}이 산업시설로 이동합니다. CTRL+클릭하면 이 산업시설을 기준으로 새로운 외부 화면을 엽니다
|
||||
STR_INDUSTRY_VIEW_CARGO_GRAPH :{BLACK}화물 그래프
|
||||
STR_INDUSTRY_VIEW_CARGO_GRAPH_TOOLTIP :{BLACK}산업시설 화물 이력 그래프를 보여줍니다
|
||||
STR_INDUSTRY_VIEW_PRODUCTION_LEVEL :{BLACK}생산 수준: {YELLOW}{COMMA}%
|
||||
STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE :{YELLOW}산업시설이 곧 폐쇄됩니다!
|
||||
|
||||
|
@ -4999,6 +5004,7 @@ STR_ERROR_FLAT_LAND_REQUIRED :{WHITE}평지
|
|||
STR_ERROR_LAND_SLOPED_IN_WRONG_DIRECTION :{WHITE}잘못된 방향으로 땅이 기울어졌습니다
|
||||
STR_ERROR_CAN_T_DO_THIS :{WHITE}그렇게 할 수 없습니다...
|
||||
STR_ERROR_BUILDING_MUST_BE_DEMOLISHED :{WHITE}건물을 먼저 제거해야 합니다
|
||||
STR_ERROR_BUILDING_IS_PROTECTED :{WHITE}... 건물이 보호되어 있습니다
|
||||
STR_ERROR_CAN_T_CLEAR_THIS_AREA :{WHITE}이 지역을 파괴할 수 없습니다...
|
||||
STR_ERROR_SITE_UNSUITABLE :{WHITE}... 알맞지 않은 장소입니다
|
||||
STR_ERROR_ALREADY_BUILT :{WHITE}... 이미 지어져있습니다
|
||||
|
|
|
@ -635,8 +635,11 @@ STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL :{BLACK}Não mos
|
|||
STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO :{BLACK}Alternar o gráfico para este tipo de carga
|
||||
STR_GRAPH_CARGO_PAYMENT_CARGO :{TINY_FONT}{BLACK}{STRING}
|
||||
|
||||
STR_GRAPH_INDUSTRY_CAPTION :{WHITE}{INDUSTRY} - Histórico da Carga
|
||||
STR_GRAPH_INDUSTRY_RANGE_PRODUCED :Produzido
|
||||
STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED :Transportado
|
||||
STR_GRAPH_INDUSTRY_RANGE_DELIVERED :Entregue
|
||||
STR_GRAPH_INDUSTRY_RANGE_WAITING :Em Espera
|
||||
|
||||
STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP :{BLACK}Exibir classificações detalhadas de desempenho
|
||||
|
||||
|
@ -4024,6 +4027,8 @@ STR_INDUSTRY_VIEW_PRODUCTION_LAST_MONTH_TITLE :{BLACK}Produç
|
|||
STR_INDUSTRY_VIEW_PRODUCTION_LAST_MINUTE_TITLE :{BLACK}Produção no último minuto:
|
||||
STR_INDUSTRY_VIEW_TRANSPORTED :{YELLOW}{CARGO_LONG}{STRING}{BLACK} ({COMMA}% transportado)
|
||||
STR_INDUSTRY_VIEW_LOCATION_TOOLTIP :{BLACK}Centrar visualização na localização da indústria. Ctrl+Clique para abrir um novo visualizador na localização da indústria
|
||||
STR_INDUSTRY_VIEW_CARGO_GRAPH :{BLACK}Gráfico da Carga
|
||||
STR_INDUSTRY_VIEW_CARGO_GRAPH_TOOLTIP :{BLACK}Mostra o gráfico do histórico da carga desta indústria
|
||||
STR_INDUSTRY_VIEW_PRODUCTION_LEVEL :{BLACK}Nível de produção: {YELLOW}{COMMA}%
|
||||
STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE :{YELLOW}A indústria anunciou encerramento iminente!
|
||||
|
||||
|
|
|
@ -772,8 +772,11 @@ STR_GRAPH_CARGO_TOOLTIP_DISABLE_ALL :{BLACK}Скры
|
|||
STR_GRAPH_CARGO_PAYMENT_TOGGLE_CARGO :{BLACK}Включить/отключить отображение груза на графике
|
||||
STR_GRAPH_CARGO_PAYMENT_CARGO :{TINY_FONT}{BLACK}{STRING}
|
||||
|
||||
STR_GRAPH_INDUSTRY_CAPTION :{WHITE}{INDUSTRY} - Продукция
|
||||
STR_GRAPH_INDUSTRY_RANGE_PRODUCED :Произведено
|
||||
STR_GRAPH_INDUSTRY_RANGE_TRANSPORTED :Перевезено
|
||||
STR_GRAPH_INDUSTRY_RANGE_DELIVERED :Доставлено
|
||||
STR_GRAPH_INDUSTRY_RANGE_WAITING :В ожидании
|
||||
|
||||
STR_GRAPH_PERFORMANCE_DETAIL_TOOLTIP :{BLACK}Показать составляющие части рейтинга
|
||||
|
||||
|
@ -4198,6 +4201,8 @@ STR_INDUSTRY_VIEW_PRODUCTION_LAST_MONTH_TITLE :{BLACK}Прои
|
|||
STR_INDUSTRY_VIEW_PRODUCTION_LAST_MINUTE_TITLE :{BLACK}Произведено за минуту:
|
||||
STR_INDUSTRY_VIEW_TRANSPORTED :{YELLOW}{CARGO_LONG}{STRING}{BLACK} ({COMMA}% перевезено)
|
||||
STR_INDUSTRY_VIEW_LOCATION_TOOLTIP :{BLACK}Показать предприятие в основном окне. Ctrl+щелчок{NBSP}- показать в дополнительном окне.
|
||||
STR_INDUSTRY_VIEW_CARGO_GRAPH :{BLACK}График доставки
|
||||
STR_INDUSTRY_VIEW_CARGO_GRAPH_TOOLTIP :{BLACK}Показать график продукции предприятия
|
||||
STR_INDUSTRY_VIEW_PRODUCTION_LEVEL :{BLACK}Производительность: {YELLOW}{COMMA}%
|
||||
STR_INDUSTRY_VIEW_INDUSTRY_ANNOUNCED_CLOSURE :{YELLOW}Предприятие скоро закрывается!
|
||||
|
||||
|
|
|
@ -37,6 +37,12 @@ if(NOT OPTION_DEDICATED)
|
|||
CONDITION HAIKU
|
||||
)
|
||||
|
||||
add_files(
|
||||
alsamidi.cpp
|
||||
alsamidi.h
|
||||
CONDITION ALSA_FOUND
|
||||
)
|
||||
|
||||
add_files(
|
||||
midi.h
|
||||
midifile.cpp
|
||||
|
|
|
@ -0,0 +1,604 @@
|
|||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @file alsamidi.cpp Support for ALSA Linux MIDI. */
|
||||
|
||||
#include "../stdafx.h"
|
||||
#include "../openttd.h"
|
||||
#include "alsamidi.h"
|
||||
#include "../base_media_base.h"
|
||||
#include "midifile.hpp"
|
||||
#include "../debug.h"
|
||||
#include "midi.h"
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "../safeguards.h"
|
||||
|
||||
/** Factory for ALSA MIDI player. */
|
||||
static FMusicDriver_AlsaMidi iFMusicDriver_AlsaMidi;
|
||||
|
||||
std::optional<std::string_view> MusicDriver_AlsaMidi::Start(const StringList &parm)
|
||||
{
|
||||
this->playing.store(false);
|
||||
Debug(driver, 2, "ALSA MIDI: Start");
|
||||
this->dev_port = (uint)GetDriverParamInt(parm, "port", UINT_MAX);
|
||||
Debug(driver, 2, "ALSA MIDI: using MIDI device at port {}", dev_port);
|
||||
|
||||
// Open sequencer
|
||||
if (snd_seq_open(&this->seq, "default", SND_SEQ_OPEN_OUTPUT, 0) < 0) {
|
||||
return "Failed to open ALSA sequencer";
|
||||
}
|
||||
|
||||
snd_seq_set_client_name(this->seq, "OpenTTD MIDI Out");
|
||||
|
||||
// Create port
|
||||
this->seq_port = snd_seq_create_simple_port(this->seq, "MIDI Out",
|
||||
SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ,
|
||||
SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION);
|
||||
|
||||
if (this->seq_port < 0) {
|
||||
return "Failed to create ALSA sequencer port";
|
||||
}
|
||||
|
||||
// Create event queue
|
||||
Debug(driver, 2, "ALSA MIDI: Creating sequencer event queue");
|
||||
this->queue_id = snd_seq_alloc_named_queue(seq, "OpenTTD Sequencer Queue");
|
||||
if (this->queue_id < 0) {
|
||||
return "Failed to create ALSA sequencer event queue";
|
||||
}
|
||||
|
||||
// Set a slightly larger event output buffer than normal
|
||||
snd_seq_set_client_pool_output(this->seq, 1000);
|
||||
snd_seq_set_client_pool_output_room(this->seq, 1000);
|
||||
snd_seq_set_output_buffer_size(this->seq, 1000);
|
||||
|
||||
// Turn on nonblocking mode
|
||||
snd_seq_nonblock(this->seq, 1);
|
||||
|
||||
snd_seq_addr_t sender, dest;
|
||||
|
||||
// Setup event port
|
||||
sender.client = snd_seq_client_id(this->seq);
|
||||
sender.port = this->seq_port;
|
||||
|
||||
dest.client = this->dev_port;
|
||||
dest.port = this->seq_port;
|
||||
|
||||
snd_seq_port_subscribe_t* subs;
|
||||
snd_seq_port_subscribe_alloca(&subs);
|
||||
snd_seq_port_subscribe_set_sender(subs, &sender);
|
||||
snd_seq_port_subscribe_set_dest(subs, &dest);
|
||||
|
||||
if (snd_seq_subscribe_port(this->seq, subs) < 0) {
|
||||
return "Failed to connect to port";
|
||||
}
|
||||
|
||||
Debug(driver, 2, "ALSA MIDI: opened sequencer port {}", this->seq_port);
|
||||
|
||||
this->InitMidiVolume();
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void MusicDriver_AlsaMidi::Stop()
|
||||
{
|
||||
Debug(driver, 2, "ALSA MIDI: stopping");
|
||||
this->StopSong();
|
||||
|
||||
if (this->queue_id) {
|
||||
Debug(driver, 2, "ALSA MIDI: freeing sequencer event queue");
|
||||
snd_seq_free_queue(this->seq, this->queue_id);
|
||||
}
|
||||
|
||||
if (this->seq) {
|
||||
Debug(driver, 2, "ALSA MIDI: closing sequencer handle");
|
||||
snd_seq_close(this->seq);
|
||||
}
|
||||
}
|
||||
|
||||
void MusicDriver_AlsaMidi::PlaySong(const MusicSongInfo &song)
|
||||
{
|
||||
Debug(driver, 2, "ALSA MIDI: PlaySong");
|
||||
std::string filename = MidiFile::GetSMFFile(song);
|
||||
smf::MidiFile midifile;
|
||||
|
||||
Debug(driver, 2, "ALSA MIDI: reading SMFFile");
|
||||
if (!filename.empty()) {
|
||||
if (!midifile.read(filename)) {
|
||||
Debug(driver, 2, "ALSA MIDI: error reading SMFFile");
|
||||
}
|
||||
}
|
||||
|
||||
// Sort
|
||||
midifile.sortTracks();
|
||||
// Convert MIDI ticks to absolute seconds
|
||||
midifile.doTimeAnalysis();
|
||||
|
||||
// Merge > 1 tracks into single track for easier queueing.
|
||||
// (WriteSMF only creates single-track MIDIs, other packs may be multitrack)
|
||||
midifile.joinTracks();
|
||||
|
||||
if (this->playing.load() == true) {
|
||||
this->StopSong();
|
||||
}
|
||||
|
||||
Debug(driver, 2, "ALSA MIDI: starting playback of {}", song.songname);
|
||||
Debug(driver, 2, "ALSA MIDI: SMF filename {}", filename);
|
||||
|
||||
// ALSA does not allow setting PPQ on started queues, so do this first.
|
||||
// Tempo may be adjusted later, on a started/running queue.
|
||||
int ppq = midifile.getTPQ();
|
||||
this->SetPPQ(ppq);
|
||||
|
||||
this->SetupPolling();
|
||||
|
||||
snd_seq_start_queue(this->seq, this->queue_id, nullptr);
|
||||
snd_seq_drain_output(this->seq);
|
||||
|
||||
this->playing.store(true);
|
||||
|
||||
StartNewThread(&this->_queue_thread, "ottd:alsamidi", &StartQueue, this, std::move(midifile));
|
||||
}
|
||||
|
||||
void MusicDriver_AlsaMidi::SetupPolling()
|
||||
{
|
||||
int poll_fd_cnt = snd_seq_poll_descriptors_count(this->seq, POLLOUT);
|
||||
this->poll_fds.resize(poll_fd_cnt);
|
||||
snd_seq_poll_descriptors(this->seq, this->poll_fds.data(), poll_fd_cnt, POLLOUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the ALSA sequencer queue, iterates through the MIDI events in the file,
|
||||
* converts them to ALSA sequencer events, and pushes them onto the queue.
|
||||
*
|
||||
* This function is blocking and expects to be run in a thread. It will block
|
||||
* until either it is signaled to stop (in which case it will purge the ALSA queue,
|
||||
* send a GM RESET, and terminate), or it has enqueued all events in the MIDI file,
|
||||
* and waited for the queue to finish processing them all.
|
||||
*
|
||||
* @param drv Pointer to `this` instance of the class
|
||||
* @param midifile The previously-loaded, sorted, and time-corrected STD MIDI file.
|
||||
*
|
||||
* @see Stopping()
|
||||
* @see StopSong()
|
||||
* @see WaitForFinish()
|
||||
*/
|
||||
static void StartQueue(MusicDriver_AlsaMidi *drv, const smf::MidiFile midifile)
|
||||
{
|
||||
Debug(driver, 2, "ALSA MIDI: queue thread started");
|
||||
|
||||
unsigned int last_tick;
|
||||
|
||||
// Push all events for all tracks to the sequencer queue
|
||||
for (int track = 0; track < midifile.getNumTracks(); track++) {
|
||||
|
||||
std::vector<uint8_t> sysex_buffer;
|
||||
|
||||
for (int event = 0; event < midifile[track].size(); event++) {
|
||||
|
||||
auto& ev = midifile[track][event];
|
||||
|
||||
last_tick = static_cast<unsigned int>(ev.tick);
|
||||
|
||||
if (drv->Stopping()) {
|
||||
Debug(driver, 2, "ALSA MIDI: Looks like we are stopping, bailing out of queue thread");
|
||||
drv->SendResetEvent();
|
||||
drv->StopQueue();
|
||||
sysex_buffer.clear();
|
||||
return;
|
||||
}
|
||||
if (ev.isTempo()) {
|
||||
// Handle tempo change here, as we have to change it for the whole queue
|
||||
Debug(driver, 2, "ALSA MIDI: Got tempo change event in queue thread");
|
||||
int tempo_uspq = ev.getTempoMicroseconds();
|
||||
drv->UpdateTempo(tempo_uspq);
|
||||
continue;
|
||||
}
|
||||
// Handle SYSEX events
|
||||
// SYSEX events may
|
||||
// 1. Be a complete SYSEX event (begin with F0 and end with F7)
|
||||
// 2. Be a "middle" SYSEX event (a previous message began with F0)
|
||||
// 3. Be an "end" SYSEX event (a previous message began with F0, and this one ends with F7)
|
||||
// This basically means you need an accumulator. Split SYSEX messages are *rare* but exist.
|
||||
if (ev.getCommandByte() == MIDIST_SYSEX) {
|
||||
Debug(driver, 2, "ALSA MIDI: got SYSEX message");
|
||||
sysex_buffer.clear();
|
||||
|
||||
// If this is is a complete (not partial) SYSEX message, send it
|
||||
// Otherwise, accumulate it as a partial and continue to the next
|
||||
if (ev.back() == MIDIST_ENDSYSEX) {
|
||||
Debug(driver, 2, "ALSA MIDI: complete SYSEX, sending");
|
||||
sysex_buffer.insert(sysex_buffer.end(), ev.begin() + 1, ev.end() -1);
|
||||
drv->SendSysexEvent(sysex_buffer);
|
||||
sysex_buffer.clear();
|
||||
} else {
|
||||
sysex_buffer.insert(sysex_buffer.end(), ev.begin() + 1, ev.end());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!sysex_buffer.empty() && ev.back() == MIDIST_ENDSYSEX) {
|
||||
Debug(driver, 2, "ALSA MIDI: partial SYSEX completed, sending");
|
||||
sysex_buffer.insert(sysex_buffer.end(), ev.begin(), ev.end() -1);
|
||||
drv->SendSysexEvent(sysex_buffer);
|
||||
sysex_buffer.clear();
|
||||
continue;
|
||||
}
|
||||
if (!sysex_buffer.empty()) {
|
||||
Debug(driver, 2, "ALSA MIDI: partial SYSEX continuing");
|
||||
sysex_buffer.insert(sysex_buffer.end(), ev.begin(), ev.end());
|
||||
continue;
|
||||
}
|
||||
|
||||
// At this point, it's just a regular event - handle it.
|
||||
drv->SendEvent(ev);
|
||||
}
|
||||
}
|
||||
|
||||
Debug(driver, 2, "ALSA MIDI: queue thread finished, waiting for events");
|
||||
drv->WaitForFinish(last_tick);
|
||||
drv->StopQueue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the ALSA sequencer queue, and sets `playing` to false.
|
||||
*
|
||||
* Note that this does not clear or drop any pending events in the queue
|
||||
* before stopping it.
|
||||
*
|
||||
* @see SendResetEvent()
|
||||
*/
|
||||
void MusicDriver_AlsaMidi::StopQueue()
|
||||
{
|
||||
Debug(driver, 2, "ALSA MIDI: stopping current queue!");
|
||||
snd_seq_stop_queue(this->seq, this->queue_id, nullptr);
|
||||
snd_seq_drain_output(this->seq);
|
||||
|
||||
this->poll_fds.clear();
|
||||
|
||||
this->playing.store(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a SYSEX GM reset message, after dropping all pending events in the
|
||||
* queue.
|
||||
*
|
||||
* Does not stop the queue.
|
||||
*
|
||||
* @see StopQueue()
|
||||
*/
|
||||
void MusicDriver_AlsaMidi::SendResetEvent()
|
||||
{
|
||||
// Drop anything still in the queue, this is a disruptive reset.
|
||||
snd_seq_drop_output(this->seq);
|
||||
|
||||
std::vector<unsigned char> sysex_rst_msg = {0x7E, 0x7F, 0x09, 0x01};
|
||||
this->SendSysexEvent(sysex_rst_msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic helper for sending SYSEX messages.
|
||||
*
|
||||
* Note that this sends all SYSEX messages as "direct"/unscheduled events
|
||||
* (skips tick queue).
|
||||
*
|
||||
* @param data The SYSEX message data (excluding 0xF0/0xF7 begin/end markers).
|
||||
*
|
||||
* @see SendEvent()
|
||||
*/
|
||||
void MusicDriver_AlsaMidi::SendSysexEvent(const std::vector<uint8_t> data)
|
||||
{
|
||||
snd_seq_event_t seqev;
|
||||
snd_seq_ev_clear(&seqev);
|
||||
snd_seq_ev_set_source(&seqev, this->seq_port);
|
||||
snd_seq_ev_set_subs(&seqev);
|
||||
|
||||
std::vector<uint8_t> complete_message;
|
||||
complete_message.reserve(data.size() + 2);
|
||||
complete_message.push_back(0xF0); // Start of SysEx
|
||||
complete_message.insert(complete_message.end(), data.begin(), data.end());
|
||||
complete_message.push_back(0xF7); // End of SysEx
|
||||
snd_seq_ev_set_sysex(&seqev, complete_message.size(), complete_message.data());
|
||||
|
||||
// TODO this assumes all SYSEX msgs are immediate, and not queued with a tick
|
||||
// this is correct for SYSEX GM RESET (all this is currently used for) but
|
||||
// might not be globally correct.
|
||||
snd_seq_ev_set_direct(&seqev);
|
||||
|
||||
this->PushEvent(seqev);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic helper for sending non-SYSEX messages.
|
||||
*
|
||||
* Converts MIDI events from the file into ALSA-specific sequencer queue events,
|
||||
* and schedules them on the tick-based sequencer queue.
|
||||
*
|
||||
* @param ev The raw MIDI event from the file.
|
||||
*
|
||||
* @see SendSysexEvent()
|
||||
*/
|
||||
void MusicDriver_AlsaMidi::SendEvent(const smf::MidiEvent& ev)
|
||||
{
|
||||
snd_seq_event_t seqev;
|
||||
snd_seq_ev_clear(&seqev);
|
||||
|
||||
unsigned int ticks = static_cast<unsigned int>(ev.tick);
|
||||
|
||||
if (ev.isNoteOn()) {
|
||||
snd_seq_ev_set_noteon(&seqev, ev.getChannel(), ev[1], ev[2]);
|
||||
} else if (ev.isNoteOff()) {
|
||||
snd_seq_ev_set_noteoff(&seqev, ev.getChannel(), ev[1], ev[2]);
|
||||
} else if (ev.isController()) {
|
||||
if (ev[1] == MIDI_CTL_MSB_MAIN_VOLUME) {
|
||||
this->UpdateChannelVolume(ev.getChannel(), ev[2]);
|
||||
snd_seq_ev_set_controller(&seqev, ev.getChannel(), ev[1], this->vol_state.current_volume[ev.getChannel()]);
|
||||
}
|
||||
snd_seq_ev_set_controller(&seqev, ev.getChannel(), ev[1], ev[2]);
|
||||
} else if (ev.isPatchChange()) {
|
||||
snd_seq_ev_set_pgmchange(&seqev, ev.getChannel(), ev[1]);
|
||||
} else if (ev.isPitchbend()) {
|
||||
snd_seq_ev_set_pitchbend(&seqev, ev.getChannel(), ((ev[2] << 7) | ev[1]) - 8192);
|
||||
} else if (ev.isPressure()) {
|
||||
snd_seq_ev_set_chanpress(&seqev, ev.getChannel(), ev[1]);
|
||||
} else if (ev.isAftertouch()) {
|
||||
snd_seq_ev_set_keypress(&seqev, ev.getChannel(), ev[1], ev[2]);
|
||||
} else if (ev.getCommandNibble() == 0xF0 && ev.getCommandByte() == MIDIST_SYSRESET) {
|
||||
Debug(driver, 2, "ALSA MIDI: reset event");
|
||||
snd_seq_ev_set_fixed(&seqev);
|
||||
seqev.type = SND_SEQ_EVENT_RESET;
|
||||
} else if (ev.isMeta()) {
|
||||
Debug(driver, 2, "ALSA MIDI: ignoring meta message");
|
||||
return;
|
||||
} else {
|
||||
Debug(driver, 2, "ALSA MIDI: unknown message: {}", ev.getCommandNibble());
|
||||
return;
|
||||
}
|
||||
|
||||
// Schedule event
|
||||
snd_seq_ev_schedule_tick(&seqev, this->queue_id, 0, ticks);
|
||||
snd_seq_ev_set_source(&seqev, this->seq_port);
|
||||
snd_seq_ev_set_subs(&seqev);
|
||||
|
||||
this->PushEvent(seqev);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until either:
|
||||
*
|
||||
* 1. The ALSA sequencer finishes processing up to the last event
|
||||
* that was enqueued, as measured by comparing the tick value of the
|
||||
* last event against the current tick value of the ALSA queue state.
|
||||
*
|
||||
* 2. `Stopping()` returns true, signaling early exit (don't wait for playback to finish).
|
||||
*
|
||||
* @param last_event_tick Tick value of the last event that was added to the queue.
|
||||
*
|
||||
* @see Stopping()
|
||||
*/
|
||||
void MusicDriver_AlsaMidi::WaitForFinish(const unsigned int last_event_tick)
|
||||
{
|
||||
Debug(driver, 2, "ALSA MIDI: waiting for events finish");
|
||||
|
||||
// First wait for queue to drain
|
||||
int res = 0;
|
||||
do {
|
||||
res = snd_seq_drain_output(this->seq);
|
||||
if (res != 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
} while (res != 0);
|
||||
|
||||
// Now get queue status and wait until we've passed the last scheduled tick
|
||||
snd_seq_queue_status_t *status;
|
||||
snd_seq_queue_status_alloca(&status);
|
||||
|
||||
do {
|
||||
snd_seq_get_queue_status(this->seq, this->queue_id, status);
|
||||
const snd_seq_tick_time_t current_tick = snd_seq_queue_status_get_tick_time(status);
|
||||
|
||||
if (this->Stopping()) {
|
||||
Debug(driver, 2, "ALSA MIDI: got stop signal, not waiting for events to finish");
|
||||
this->SendResetEvent();
|
||||
break;
|
||||
}
|
||||
|
||||
if (current_tick >= last_event_tick) {
|
||||
// This is necessarily imprecise, just because the queue has processed the last
|
||||
// tick event doesn't mean whatever output device in use has played it yet,
|
||||
// but in practice this is good enough to not cut off the last few notes.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
break;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
} while(true);
|
||||
|
||||
Debug(driver, 2, "ALSA MIDI: events finished");
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes an ALSA sequencer event onto the ALSA sequencer queue.
|
||||
*
|
||||
* If the push fails because the output buffer is full, uses `poll()`
|
||||
* to wait until there's space/device is ready.
|
||||
*
|
||||
* @param seqev ALSA sequencer event to push.
|
||||
*/
|
||||
void MusicDriver_AlsaMidi::PushEvent(snd_seq_event_t seqev)
|
||||
{
|
||||
// Wait for space in the queue via `poll()`
|
||||
while (snd_seq_event_output_direct(this->seq, &seqev) < 0) {
|
||||
poll(this->poll_fds.data(), this->poll_fds.size(), 100); // 100ms timeout
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals the queue thread to terminate and joins it.
|
||||
*/
|
||||
void MusicDriver_AlsaMidi::StopSong()
|
||||
{
|
||||
this->stopping.store(true);
|
||||
|
||||
Debug(driver, 2, "ALSA MIDI: StopSong waiting for queue thread");
|
||||
if (this->_queue_thread.joinable()) {
|
||||
this->_queue_thread.join();
|
||||
}
|
||||
|
||||
Debug(driver, 2, "ALSA MIDI: stopping current queue");
|
||||
|
||||
this->stopping.store(false);
|
||||
|
||||
Debug(driver, 2, "ALSA MIDI: stopped song");
|
||||
}
|
||||
|
||||
bool MusicDriver_AlsaMidi::Stopping()
|
||||
{
|
||||
return this->stopping.load();
|
||||
}
|
||||
|
||||
bool MusicDriver_AlsaMidi::IsSongPlaying()
|
||||
{
|
||||
return this->playing.load();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the desired volume for the MIDI sequencer (from the UI thread)
|
||||
*
|
||||
* Note that this implementation will internally debounce rapid subsequent calls to
|
||||
* SetVolume(), to avoid overwhelming the sequencer and its queues and buffers with
|
||||
* incremental volume updates. The magnitude of the volume change is taken into account
|
||||
* for this.
|
||||
*
|
||||
*/
|
||||
void MusicDriver_AlsaMidi::SetVolume(uint8_t vol)
|
||||
{
|
||||
Debug(driver, 2, "ALSA MIDI: got volume level update {}", vol);
|
||||
|
||||
// Adaptive debounce: small changes need more time between updates
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_volume_update);
|
||||
|
||||
int current_vol = this->current_vol.load();
|
||||
int change_magnitude = std::abs(vol - current_vol);
|
||||
|
||||
int required_ms = change_magnitude < 5 ? SMALL_VOL_DEBOUNCE :
|
||||
change_magnitude < 15 ? MED_VOL_DEBOUNCE :
|
||||
LARGE_VOL_DEBOUNCE;
|
||||
|
||||
|
||||
if (elapsed.count() >= required_ms || current_vol == 127) {
|
||||
Debug(driver, 2, "ALSA MIDI: got volume level update {}", vol);
|
||||
if (vol != current_vol) {
|
||||
this->SetScaledVolume(vol);
|
||||
this->last_volume_update = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the volume state for the player.
|
||||
*
|
||||
* @see UpdateChannelVolume()
|
||||
* @see SetScaledVolume()
|
||||
*/
|
||||
void MusicDriver_AlsaMidi::InitMidiVolume()
|
||||
{
|
||||
this->last_volume_update = std::chrono::steady_clock::now();
|
||||
|
||||
this->vol_state.master_scale = 127;
|
||||
for (int i = 0; i < MIDI_CHANNELS; i++) {
|
||||
this->vol_state.base_volume[i] = 127;
|
||||
this->vol_state.current_volume[i] = 127;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scales current volume level for each channel according to the scale
|
||||
* factor provided. This is to maintain the relative volume levels between
|
||||
* channels as set by the midi file, while scaling up or down.
|
||||
*
|
||||
* @param channel to update the volume for.
|
||||
* @param value requested volume level to scale selected channel volume against.
|
||||
*/
|
||||
void MusicDriver_AlsaMidi::UpdateChannelVolume(int channel, int value)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->vol_state.mutex);
|
||||
this->vol_state.base_volume[channel] = value;
|
||||
this->vol_state.current_volume[channel] = (value * this->vol_state.master_scale) / 127;
|
||||
Debug(driver, 2, "ALSA MIDI: upading volume for channel {} to {}, base: {}, scale {}", channel, this->vol_state.current_volume[channel], this->vol_state.base_volume[channel], this->vol_state.master_scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scales current volume level for each channel according to the scale
|
||||
* factor provided. This is to maintain the relative volume levels between
|
||||
* channels as set by the midi file, while scaling up or down.
|
||||
*
|
||||
* @param value requested volume level (0-127) to scale all channel volume levels against.
|
||||
*/
|
||||
void MusicDriver_AlsaMidi::SetScaledVolume(int value)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->vol_state.mutex);
|
||||
this->vol_state.master_scale = (value > 127) ? 127 : (value < 0) ? 0 : value;
|
||||
|
||||
for (int i = 0; i < MIDI_CHANNELS; i++) {
|
||||
this->vol_state.current_volume[i] = ( this->vol_state.base_volume[i] * this->vol_state.master_scale) / 127;
|
||||
snd_seq_event_t vol_ev;
|
||||
snd_seq_ev_clear(&vol_ev);
|
||||
Debug(driver, 2, "ALSA MIDI: setting volume for channel {} to {}, master: {} base: {}", i, this->vol_state.current_volume[i], this->vol_state.master_scale, this->vol_state.base_volume[i]);
|
||||
snd_seq_ev_set_controller(&vol_ev, i, MIDI_CTL_MSB_MAIN_VOLUME, this->vol_state.current_volume[i]);
|
||||
snd_seq_ev_set_source(&vol_ev, this->seq_port);
|
||||
snd_seq_ev_set_subs(&vol_ev);
|
||||
snd_seq_ev_set_direct(&vol_ev);
|
||||
this->PushEvent(vol_ev);
|
||||
}
|
||||
|
||||
this->current_vol.store(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the tempo of the current (started) ALSA sequencer queue.
|
||||
*
|
||||
* @param tempo_uspq Tempo value in units per quarter note.
|
||||
*/
|
||||
void MusicDriver_AlsaMidi::UpdateTempo(const int tempo_uspq)
|
||||
{
|
||||
if (snd_seq_change_queue_tempo(this->seq, this->queue_id, tempo_uspq, nullptr) < 0) {
|
||||
throw std::runtime_error("Failed to update queue tempo");
|
||||
}
|
||||
snd_seq_drain_output(this->seq);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Pulses Per Quarternote (PPQ) of the current ALSA sequencer queue.
|
||||
*
|
||||
* Note that the PPQ of an ALSA sequencer queue cannot be changed after it is started.
|
||||
*
|
||||
* @param ppq Pulse per quarter note value.
|
||||
*
|
||||
* @see StopQueue()
|
||||
*/
|
||||
void MusicDriver_AlsaMidi::SetPPQ(const int ppq)
|
||||
{
|
||||
Debug(driver, 2, "ALSA MIDI: setting PPQ to {}", ppq);
|
||||
snd_seq_queue_tempo_t* tempo;
|
||||
snd_seq_queue_tempo_alloca(&tempo);
|
||||
snd_seq_queue_tempo_set_ppq(tempo, ppq);
|
||||
snd_seq_queue_tempo_set_tempo(tempo, 1000000); // 60 BPM
|
||||
|
||||
snd_seq_queue_status_t *status;
|
||||
snd_seq_queue_status_alloca(&status);
|
||||
snd_seq_get_queue_status(seq, queue_id, status);
|
||||
|
||||
if (snd_seq_queue_status_get_status(status) == 0) {
|
||||
if (snd_seq_set_queue_tempo(this->seq, this->queue_id, tempo) < 0) {
|
||||
throw std::runtime_error("Failed to set queue PPQ");
|
||||
}
|
||||
snd_seq_drain_output(this->seq);
|
||||
} else {
|
||||
Debug(driver, 2, "ALSA MIDI: tried to set PPQ on non-stopped queue!");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @file alsamidi.h Base of Alsa MIDI support. */
|
||||
|
||||
#ifndef MUSIC_ALSAMIDI_H
|
||||
#define MUSIC_ALSAMIDI_H
|
||||
|
||||
#include "music_driver.hpp"
|
||||
#include <alsa/asoundlib.h>
|
||||
#include "../3rdparty/midifile/MidiFile.h"
|
||||
#include "../../thread.h"
|
||||
#include <atomic>
|
||||
|
||||
const int SMALL_VOL_DEBOUNCE = 200; // Tiny changes: 200ms
|
||||
const int MED_VOL_DEBOUNCE = 50; // Small changes: 50ms
|
||||
const int LARGE_VOL_DEBOUNCE = 10; // Large changes: 10ms
|
||||
|
||||
/** The midi player for ALSA on Linux. */
|
||||
class MusicDriver_AlsaMidi : public MusicDriver {
|
||||
public:
|
||||
std::optional<std::string_view> Start(const StringList ¶m) override;
|
||||
|
||||
void Stop() override;
|
||||
|
||||
void PlaySong(const MusicSongInfo &song) override;
|
||||
|
||||
void StopSong() override;
|
||||
|
||||
bool IsSongPlaying() override;
|
||||
|
||||
void SetVolume(uint8_t vol) override;
|
||||
|
||||
std::string_view GetName() const override { return "alsamidi"; }
|
||||
|
||||
void WaitForFinish(const unsigned int last_tick);
|
||||
|
||||
void StopQueue();
|
||||
|
||||
void SendEvent(const smf::MidiEvent& event);
|
||||
|
||||
void SendSysexEvent(const std::vector<uint8_t> data);
|
||||
|
||||
void SendResetEvent();
|
||||
|
||||
void UpdateTempo(const int tempo_uspq);
|
||||
|
||||
bool Stopping();
|
||||
|
||||
|
||||
private:
|
||||
typedef struct
|
||||
{
|
||||
int base_volume[MIDI_CHANNELS];
|
||||
int master_scale;
|
||||
int current_volume[MIDI_CHANNELS];
|
||||
std::mutex mutex;
|
||||
}MidiVolume;
|
||||
|
||||
MidiVolume vol_state;
|
||||
snd_seq_t* seq;
|
||||
int queue_id;
|
||||
int seq_port;
|
||||
int dev_port;
|
||||
std::vector<struct pollfd> poll_fds;
|
||||
std::thread _queue_thread;
|
||||
std::atomic<bool> playing{false};
|
||||
std::atomic<bool> stopping{false};
|
||||
std::atomic<uint8_t> current_vol{127};
|
||||
std::chrono::time_point<std::chrono::steady_clock> last_volume_update;
|
||||
void SetPPQ(const int ppq);
|
||||
void SetScaledVolume(int value);
|
||||
void SetupPolling();
|
||||
void PushEvent(snd_seq_event_t seqev);
|
||||
void InitMidiVolume();
|
||||
void UpdateChannelVolume(int channel, int value);
|
||||
void VolumeAdjust (const uint8_t new_vol);
|
||||
};
|
||||
|
||||
/** Factory for the Linux ALSA midi player. */
|
||||
class FMusicDriver_AlsaMidi : public DriverFactoryBase {
|
||||
public:
|
||||
FMusicDriver_AlsaMidi() : DriverFactoryBase(Driver::DT_MUSIC, 10, "alsamidi", "ALSA Linux MIDI Driver") {}
|
||||
Driver *CreateInstance() const override { return new MusicDriver_AlsaMidi(); }
|
||||
};
|
||||
|
||||
static void StartQueue(MusicDriver_AlsaMidi *drv, const smf::MidiFile midifile);
|
||||
|
||||
#endif /* MUSIC_ALSAMIDI_H */
|
|
@ -90,12 +90,12 @@ static ChangeInfoResult StationChangeInfo(uint first, uint last, int prop, ByteR
|
|||
|
||||
/* no relative bounding box support */
|
||||
DrawTileSeqStruct &dtss = tmp_layout.emplace_back();
|
||||
dtss.delta_x = delta_x;
|
||||
dtss.delta_y = buf.ReadByte();
|
||||
dtss.delta_z = buf.ReadByte();
|
||||
dtss.size_x = buf.ReadByte();
|
||||
dtss.size_y = buf.ReadByte();
|
||||
dtss.size_z = buf.ReadByte();
|
||||
dtss.origin.x = delta_x;
|
||||
dtss.origin.y = buf.ReadByte();
|
||||
dtss.origin.z = buf.ReadByte();
|
||||
dtss.extent.x = buf.ReadByte();
|
||||
dtss.extent.y = buf.ReadByte();
|
||||
dtss.extent.z = buf.ReadByte();
|
||||
|
||||
ReadSpriteLayoutSprite(buf, false, true, false, GSF_STATIONS, &dtss.image);
|
||||
/* On error, bail out immediately. Temporary GRF data was already freed */
|
||||
|
|
|
@ -207,15 +207,15 @@ bool ReadSpriteLayout(ByteReader &buf, uint num_building_sprites, bool use_cur_s
|
|||
return true;
|
||||
}
|
||||
|
||||
seq->delta_x = buf.ReadByte();
|
||||
seq->delta_y = buf.ReadByte();
|
||||
seq->origin.x = buf.ReadByte();
|
||||
seq->origin.y = buf.ReadByte();
|
||||
|
||||
if (!no_z_position) seq->delta_z = buf.ReadByte();
|
||||
if (!no_z_position) seq->origin.z = buf.ReadByte();
|
||||
|
||||
if (seq->IsParentSprite()) {
|
||||
seq->size_x = buf.ReadByte();
|
||||
seq->size_y = buf.ReadByte();
|
||||
seq->size_z = buf.ReadByte();
|
||||
seq->extent.x = buf.ReadByte();
|
||||
seq->extent.y = buf.ReadByte();
|
||||
seq->extent.z = buf.ReadByte();
|
||||
}
|
||||
|
||||
ReadSpriteLayoutRegisters(buf, flags, seq->IsParentSprite(), dts, i + 1);
|
||||
|
|
|
@ -609,7 +609,7 @@ SpriteLayoutProcessor::SpriteLayoutProcessor(const NewGRFSpriteLayout &raw_layou
|
|||
* Also include the groundsprite into the sequence for easier processing. */
|
||||
DrawTileSeqStruct © = this->result_seq.emplace_back();
|
||||
copy.image = this->raw_layout->ground;
|
||||
copy.delta_z = static_cast<int8_t>(0x80);
|
||||
copy.origin.z = static_cast<int8_t>(0x80);
|
||||
|
||||
this->result_seq.insert(this->result_seq.end(), this->raw_layout->seq.begin(), this->raw_layout->seq.end());
|
||||
|
||||
|
@ -692,13 +692,13 @@ void SpriteLayoutProcessor::ProcessRegisters(const ResolverObject &object, uint8
|
|||
|
||||
if (result.IsParentSprite()) {
|
||||
if (flags & TLF_BB_XY_OFFSET) {
|
||||
result.delta_x += object.GetRegister(regs->delta.parent[0]);
|
||||
result.delta_y += object.GetRegister(regs->delta.parent[1]);
|
||||
result.origin.x += object.GetRegister(regs->delta.parent[0]);
|
||||
result.origin.y += object.GetRegister(regs->delta.parent[1]);
|
||||
}
|
||||
if (flags & TLF_BB_Z_OFFSET) result.delta_z += object.GetRegister(regs->delta.parent[2]);
|
||||
if (flags & TLF_BB_Z_OFFSET) result.origin.z += object.GetRegister(regs->delta.parent[2]);
|
||||
} else {
|
||||
if (flags & TLF_CHILD_X_OFFSET) result.delta_x += object.GetRegister(regs->delta.child[0]);
|
||||
if (flags & TLF_CHILD_Y_OFFSET) result.delta_y += object.GetRegister(regs->delta.child[1]);
|
||||
if (flags & TLF_CHILD_X_OFFSET) result.origin.x += object.GetRegister(regs->delta.child[0]);
|
||||
if (flags & TLF_CHILD_Y_OFFSET) result.origin.y += object.GetRegister(regs->delta.child[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -476,13 +476,7 @@ static void DrawTile_Object(TileInfo *ti)
|
|||
|
||||
if (!IsInvisibilitySet(TO_STRUCTURES)) {
|
||||
for (const DrawTileSeqStruct &dtss : dts->GetSequence()) {
|
||||
AddSortableSpriteToDraw(
|
||||
dtss.image.sprite, palette,
|
||||
ti->x + dtss.delta_x, ti->y + dtss.delta_y,
|
||||
dtss.size_x, dtss.size_y,
|
||||
dtss.size_z, ti->z + dtss.delta_z,
|
||||
IsTransparencySet(TO_STRUCTURES)
|
||||
);
|
||||
AddSortableSpriteToDraw(dtss.image.sprite, palette, *ti, dtss, IsTransparencySet(TO_STRUCTURES));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -1899,7 +1899,7 @@ static void DrawSingleSignal(TileIndex tile, const RailTypeInfo *rti, Track trac
|
|||
sprite += type * 16 + variant * 64 + image * 2 + condition + (type > SIGTYPE_LAST_NOPBS ? 64 : 0);
|
||||
}
|
||||
|
||||
AddSortableSpriteToDraw(sprite, PAL_NONE, x, y, 1, 1, BB_HEIGHT_UNDER_BRIDGE, GetSaveSlopeZ(x, y, track));
|
||||
AddSortableSpriteToDraw(sprite, PAL_NONE, x, y, GetSaveSlopeZ(x, y, track), {{}, {1, 1, BB_HEIGHT_UNDER_BRIDGE}, {}});
|
||||
}
|
||||
|
||||
static uint32_t _drawtile_track_palette;
|
||||
|
@ -1907,12 +1907,11 @@ static uint32_t _drawtile_track_palette;
|
|||
|
||||
|
||||
/** Offsets for drawing fences */
|
||||
struct FenceOffset {
|
||||
Corner height_ref; //!< Corner to use height offset from.
|
||||
int x_offs; //!< Bounding box X offset.
|
||||
int y_offs; //!< Bounding box Y offset.
|
||||
int x_size; //!< Bounding box X size.
|
||||
int y_size; //!< Bounding box Y size.
|
||||
struct FenceOffset : SpriteBounds {
|
||||
Corner height_ref; ///< Corner to use height offset from.
|
||||
|
||||
constexpr FenceOffset(Corner height_ref, int8_t origin_x, int8_t origin_y, uint8_t extent_x, uint8_t extent_y) :
|
||||
SpriteBounds({origin_x, origin_y, 0}, {extent_x, extent_y, 4}, {}), height_ref(height_ref) {}
|
||||
};
|
||||
|
||||
/** Offsets for drawing fences */
|
||||
|
@ -1948,12 +1947,7 @@ static void DrawTrackFence(const TileInfo *ti, SpriteID base_image, uint num_spr
|
|||
if (_fence_offsets[rfo].height_ref != CORNER_INVALID) {
|
||||
z += GetSlopePixelZInCorner(RemoveHalftileSlope(ti->tileh), _fence_offsets[rfo].height_ref);
|
||||
}
|
||||
AddSortableSpriteToDraw(base_image + (rfo % num_sprites), _drawtile_track_palette,
|
||||
ti->x + _fence_offsets[rfo].x_offs,
|
||||
ti->y + _fence_offsets[rfo].y_offs,
|
||||
_fence_offsets[rfo].x_size,
|
||||
_fence_offsets[rfo].y_size,
|
||||
4, z);
|
||||
AddSortableSpriteToDraw(base_image + (rfo % num_sprites), _drawtile_track_palette, ti->x, ti->y, z, _fence_offsets[rfo]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1418,7 +1418,7 @@ void DrawRoadTypeCatenary(const TileInfo *ti, RoadType rt, RoadBits rb)
|
|||
* For tiles with OWNER_TOWN or OWNER_NONE, recolour CC to grey as a neutral colour. */
|
||||
Owner owner = GetRoadOwner(ti->tile, GetRoadTramType(rt));
|
||||
PaletteID pal = (owner == OWNER_NONE || owner == OWNER_TOWN ? GetColourPalette(COLOUR_GREY) : GetCompanyPalette(owner));
|
||||
int z_wires = (ti->tileh == SLOPE_FLAT ? 0 : TILE_HEIGHT) + BB_HEIGHT_UNDER_BRIDGE;
|
||||
uint8_t z_wires = (ti->tileh == SLOPE_FLAT ? 0 : TILE_HEIGHT) + BB_HEIGHT_UNDER_BRIDGE;
|
||||
if (back != 0) {
|
||||
/* The "back" sprite contains the west, north and east pillars.
|
||||
* We cut the sprite at 3/8 of the west/east edges to create 3 sprites.
|
||||
|
@ -1427,13 +1427,16 @@ void DrawRoadTypeCatenary(const TileInfo *ti, RoadType rt, RoadBits rb)
|
|||
static const SubSprite west = { -INF, -INF, -12, INF };
|
||||
static const SubSprite north = { -12, -INF, 12, INF };
|
||||
static const SubSprite east = { 12, -INF, INF, INF };
|
||||
AddSortableSpriteToDraw(back, pal, ti->x, ti->y, 16, 1, z_wires, ti->z, IsTransparencySet(TO_CATENARY), 15, 0, GetSlopePixelZInCorner(ti->tileh, CORNER_W), &west);
|
||||
AddSortableSpriteToDraw(back, pal, ti->x, ti->y, 1, 1, z_wires, ti->z, IsTransparencySet(TO_CATENARY), 0, 0, GetSlopePixelZInCorner(ti->tileh, CORNER_N), &north);
|
||||
AddSortableSpriteToDraw(back, pal, ti->x, ti->y, 1, 16, z_wires, ti->z, IsTransparencySet(TO_CATENARY), 0, 15, GetSlopePixelZInCorner(ti->tileh, CORNER_E), &east);
|
||||
int8_t west_z = GetSlopePixelZInCorner(ti->tileh, CORNER_W);
|
||||
int8_t north_z = GetSlopePixelZInCorner(ti->tileh, CORNER_N);
|
||||
int8_t east_z = GetSlopePixelZInCorner(ti->tileh, CORNER_E);
|
||||
AddSortableSpriteToDraw(back, pal, *ti, {{15, 0, west_z}, {1, 1, z_wires}, {-15, 0, static_cast<int8_t>(-west_z)}}, IsTransparencySet(TO_CATENARY), &west);
|
||||
AddSortableSpriteToDraw(back, pal, *ti, {{0, 0, north_z}, {1, 1, z_wires}, {0, 0, static_cast<int8_t>(-north_z)}}, IsTransparencySet(TO_CATENARY), &north);
|
||||
AddSortableSpriteToDraw(back, pal, *ti, {{0, 15, east_z}, {1, 1, z_wires}, {0, -15, static_cast<int8_t>(-east_z)}}, IsTransparencySet(TO_CATENARY), &east);
|
||||
}
|
||||
if (front != 0) {
|
||||
/* Draw the "front" sprite (containing south pillar and wires) at a Z height that is both above the vehicles and above the "back" pillars. */
|
||||
AddSortableSpriteToDraw(front, pal, ti->x, ti->y, 16, 16, z_wires + 1, ti->z, IsTransparencySet(TO_CATENARY), 0, 0, z_wires);
|
||||
AddSortableSpriteToDraw(front, pal, *ti, {{0, 0, static_cast<int8_t>(z_wires)}, {TILE_SIZE, TILE_SIZE, 1}, {0, 0, static_cast<int8_t>(-z_wires)}}, IsTransparencySet(TO_CATENARY));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1487,13 +1490,13 @@ void DrawRoadCatenary(const TileInfo *ti)
|
|||
* @param h the height of the sprite to draw
|
||||
* @param transparent whether the sprite should be transparent (used for roadside trees)
|
||||
*/
|
||||
static void DrawRoadDetail(SpriteID img, const TileInfo *ti, int dx, int dy, int h, bool transparent)
|
||||
static void DrawRoadDetail(SpriteID img, const TileInfo *ti, int8_t dx, int8_t dy, uint8_t h, bool transparent)
|
||||
{
|
||||
int x = ti->x | dx;
|
||||
int y = ti->y | dy;
|
||||
int z = ti->z;
|
||||
if (ti->tileh != SLOPE_FLAT) z = GetSlopePixelZ(x, y);
|
||||
AddSortableSpriteToDraw(img, PAL_NONE, x, y, 2, 2, h, z, transparent);
|
||||
AddSortableSpriteToDraw(img, PAL_NONE, ti->x, ti->y, z, {{dx, dy, 0}, {2, 2, h}, {}}, transparent);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -405,29 +405,56 @@ void RoadVehicle::MarkDirty()
|
|||
|
||||
void RoadVehicle::UpdateDeltaXY()
|
||||
{
|
||||
static const int8_t _delta_xy_table[8][10] = {
|
||||
/* y_extent, x_extent, y_offs, x_offs, y_bb_offs, x_bb_offs, y_extent_shorten, x_extent_shorten, y_bb_offs_shorten, x_bb_offs_shorten */
|
||||
{3, 3, -1, -1, 0, 0, -1, -1, -1, -1}, // N
|
||||
{3, 7, -1, -3, 0, -1, 0, -1, 0, 0}, // NE
|
||||
{3, 3, -1, -1, 0, 0, 1, -1, 1, -1}, // E
|
||||
{7, 3, -3, -1, -1, 0, 0, 0, 1, 0}, // SE
|
||||
{3, 3, -1, -1, 0, 0, 1, 1, 1, 1}, // S
|
||||
{3, 7, -1, -3, 0, -1, 0, 0, 0, 1}, // SW
|
||||
{3, 3, -1, -1, 0, 0, -1, 1, -1, 1}, // W
|
||||
{7, 3, -3, -1, -1, 0, -1, 0, 0, 0}, // NW
|
||||
};
|
||||
/* Set common defaults. */
|
||||
this->bounds = {{-1, -1, 0}, {3, 3, 6}, {}};
|
||||
|
||||
int shorten = VEHICLE_LENGTH - this->gcache.cached_veh_length;
|
||||
if (!IsDiagonalDirection(this->direction)) shorten >>= 1;
|
||||
if (!IsDiagonalDirection(this->direction)) {
|
||||
static const Point _sign_table[] = {
|
||||
/* x, y */
|
||||
{-1, -1}, // DIR_N
|
||||
{-1, 1}, // DIR_E
|
||||
{ 1, 1}, // DIR_S
|
||||
{ 1, -1}, // DIR_W
|
||||
};
|
||||
|
||||
const int8_t *bb = _delta_xy_table[this->direction];
|
||||
this->x_bb_offs = bb[5] + bb[9] * shorten;
|
||||
this->y_bb_offs = bb[4] + bb[8] * shorten;;
|
||||
this->x_offs = bb[3];
|
||||
this->y_offs = bb[2];
|
||||
this->x_extent = bb[1] + bb[7] * shorten;
|
||||
this->y_extent = bb[0] + bb[6] * shorten;
|
||||
this->z_extent = 6;
|
||||
int half_shorten = (VEHICLE_LENGTH - this->gcache.cached_veh_length) / 2;
|
||||
|
||||
/* For all straight directions, move the bound box to the centre of the vehicle, but keep the size. */
|
||||
this->bounds.offset.x -= half_shorten * _sign_table[DirToDiagDir(this->direction)].x;
|
||||
this->bounds.offset.y -= half_shorten * _sign_table[DirToDiagDir(this->direction)].y;
|
||||
} else {
|
||||
/* Unlike trains, road vehicles do not have their offsets moved to the centre. */
|
||||
switch (this->direction) {
|
||||
/* Shorten southern corner of the bounding box according the vehicle length. */
|
||||
case DIR_NE:
|
||||
this->bounds.origin.x = -3;
|
||||
this->bounds.extent.x = this->gcache.cached_veh_length;
|
||||
this->bounds.offset.x = 1;
|
||||
break;
|
||||
|
||||
case DIR_NW:
|
||||
this->bounds.origin.y = -3;
|
||||
this->bounds.extent.y = this->gcache.cached_veh_length;
|
||||
this->bounds.offset.y = 1;
|
||||
break;
|
||||
|
||||
/* Move northern corner of the bounding box down according to vehicle length. */
|
||||
case DIR_SW:
|
||||
this->bounds.origin.x = -3 + (VEHICLE_LENGTH - this->gcache.cached_veh_length);
|
||||
this->bounds.extent.x = this->gcache.cached_veh_length;
|
||||
this->bounds.offset.x = 1 - (VEHICLE_LENGTH - this->gcache.cached_veh_length);
|
||||
break;
|
||||
|
||||
case DIR_SE:
|
||||
this->bounds.origin.y = -3 + (VEHICLE_LENGTH - this->gcache.cached_veh_length);
|
||||
this->bounds.extent.y = this->gcache.cached_veh_length;
|
||||
this->bounds.offset.y = 1 - (VEHICLE_LENGTH - this->gcache.cached_veh_length);
|
||||
break;
|
||||
|
||||
default:
|
||||
NOT_REACHED();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -330,32 +330,26 @@ TileIndex Ship::GetOrderStationLocation(StationID station)
|
|||
|
||||
void Ship::UpdateDeltaXY()
|
||||
{
|
||||
static const int8_t _delta_xy_table[8][4] = {
|
||||
/* y_extent, x_extent, y_offs, x_offs */
|
||||
{ 6, 6, -3, -3}, // N
|
||||
{ 6, 32, -3, -16}, // NE
|
||||
{ 6, 6, -3, -3}, // E
|
||||
{32, 6, -16, -3}, // SE
|
||||
{ 6, 6, -3, -3}, // S
|
||||
{ 6, 32, -3, -16}, // SW
|
||||
{ 6, 6, -3, -3}, // W
|
||||
{32, 6, -16, -3}, // NW
|
||||
static constexpr SpriteBounds ship_bounds[DIR_END] = {
|
||||
{{ -3, -3, 0}, { 6, 6, 6}, {}}, // N
|
||||
{{-16, -3, 0}, {32, 6, 6}, {}}, // NE
|
||||
{{ -3, -3, 0}, { 6, 6, 6}, {}}, // E
|
||||
{{ -3, -16, 0}, { 6, 32, 6}, {}}, // SE
|
||||
{{ -3, -3, 0}, { 6, 6, 6}, {}}, // S
|
||||
{{-16, -3, 0}, {32, 6, 6}, {}}, // SW
|
||||
{{ -3, -3, 0}, { 6, 6, 6}, {}}, // W
|
||||
{{ -3, -16, 0}, { 6, 32, 6}, {}}, // NW
|
||||
};
|
||||
|
||||
const int8_t *bb = _delta_xy_table[this->rotation];
|
||||
this->x_offs = bb[3];
|
||||
this->y_offs = bb[2];
|
||||
this->x_extent = bb[1];
|
||||
this->y_extent = bb[0];
|
||||
this->z_extent = 6;
|
||||
this->bounds = ship_bounds[this->rotation];
|
||||
|
||||
if (this->direction != this->rotation) {
|
||||
/* If we are rotating, then it is possible the ship was moved to its next position. In that
|
||||
* case, because we are still showing the old direction, the ship will appear to glitch sideways
|
||||
* slightly. We can work around this by applying an additional offset to make the ship appear
|
||||
* where it was before it moved. */
|
||||
this->x_offs -= this->x_pos - this->rotation_x_pos;
|
||||
this->y_offs -= this->y_pos - this->rotation_y_pos;
|
||||
this->bounds.origin.x -= this->x_pos - this->rotation_x_pos;
|
||||
this->bounds.origin.y -= this->y_pos - this->rotation_y_pos;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -54,16 +54,11 @@ void DrawCommonTileSeq(const TileInfo *ti, const DrawTileSprites *dts, Transpare
|
|||
|
||||
if (dtss.IsParentSprite()) {
|
||||
parent_sprite_encountered = true;
|
||||
AddSortableSpriteToDraw(
|
||||
image, pal,
|
||||
ti->x + dtss.delta_x, ti->y + dtss.delta_y,
|
||||
dtss.size_x, dtss.size_y,
|
||||
dtss.size_z, ti->z + dtss.delta_z,
|
||||
!HasBit(image, SPRITE_MODIFIER_OPAQUE) && IsTransparencySet(to)
|
||||
AddSortableSpriteToDraw(image, pal, *ti, dtss, !HasBit(image, SPRITE_MODIFIER_OPAQUE) && IsTransparencySet(to)
|
||||
);
|
||||
} else {
|
||||
int offs_x = child_offset_is_unsigned ? static_cast<uint8_t>(dtss.delta_x) : dtss.delta_x;
|
||||
int offs_y = child_offset_is_unsigned ? static_cast<uint8_t>(dtss.delta_y) : dtss.delta_y;
|
||||
int offs_x = child_offset_is_unsigned ? static_cast<uint8_t>(dtss.origin.x) : dtss.origin.x;
|
||||
int offs_y = child_offset_is_unsigned ? static_cast<uint8_t>(dtss.origin.y) : dtss.origin.y;
|
||||
bool transparent = !HasBit(image, SPRITE_MODIFIER_OPAQUE) && IsTransparencySet(to);
|
||||
if (parent_sprite_encountered) {
|
||||
AddChildSpriteScreen(image, pal, offs_x, offs_y, transparent);
|
||||
|
@ -114,15 +109,15 @@ void DrawCommonTileSeqInGUI(int x, int y, const DrawTileSprites *dts, int32_t or
|
|||
pal = SpriteLayoutPaletteTransform(image, pal, default_palette);
|
||||
|
||||
if (dtss.IsParentSprite()) {
|
||||
Point pt = RemapCoords(dtss.delta_x, dtss.delta_y, dtss.delta_z);
|
||||
Point pt = RemapCoords(dtss.origin.x, dtss.origin.y, dtss.origin.z);
|
||||
DrawSprite(image, pal, x + UnScaleGUI(pt.x), y + UnScaleGUI(pt.y));
|
||||
|
||||
const Sprite *spr = GetSprite(image & SPRITE_MASK, SpriteType::Normal);
|
||||
child_offset.x = UnScaleGUI(pt.x + spr->x_offs);
|
||||
child_offset.y = UnScaleGUI(pt.y + spr->y_offs);
|
||||
} else {
|
||||
int offs_x = child_offset_is_unsigned ? static_cast<uint8_t>(dtss.delta_x) : dtss.delta_x;
|
||||
int offs_y = child_offset_is_unsigned ? static_cast<uint8_t>(dtss.delta_y) : dtss.delta_y;
|
||||
int offs_x = child_offset_is_unsigned ? static_cast<uint8_t>(dtss.origin.x) : dtss.origin.x;
|
||||
int offs_y = child_offset_is_unsigned ? static_cast<uint8_t>(dtss.origin.y) : dtss.origin.y;
|
||||
DrawSprite(image, pal, x + child_offset.x + ScaleSpriteTrad(offs_x), y + child_offset.y + ScaleSpriteTrad(offs_y));
|
||||
}
|
||||
}
|
||||
|
|
36
src/sprite.h
36
src/sprite.h
|
@ -10,28 +10,37 @@
|
|||
#ifndef SPRITE_H
|
||||
#define SPRITE_H
|
||||
|
||||
#include "core/geometry_type.hpp"
|
||||
#include "transparency.h"
|
||||
|
||||
#include "table/sprites.h"
|
||||
|
||||
struct SpriteBounds {
|
||||
Coord3D<int8_t> origin; ///< Position of northern corner within tile.
|
||||
Coord3D<uint8_t> extent; ///< Size of bounding box.
|
||||
Coord3D<int8_t> offset; ///< Relative position of sprite from bounding box.
|
||||
|
||||
constexpr SpriteBounds() = default;
|
||||
constexpr SpriteBounds(const Coord3D<int8_t> &origin, const Coord3D<uint8_t> &extent, const Coord3D<int8_t> &offset) :
|
||||
origin(origin), extent(extent), offset(offset) {}
|
||||
};
|
||||
|
||||
/* The following describes bunch of sprites to be drawn together in a single 3D
|
||||
* bounding box. Used especially for various multi-sprite buildings (like
|
||||
* depots or stations): */
|
||||
|
||||
/** A tile child sprite and palette to draw for stations etc, with 3D bounding box */
|
||||
struct DrawTileSeqStruct {
|
||||
int8_t delta_x = 0;
|
||||
int8_t delta_y = 0;
|
||||
int8_t delta_z = 0; ///< \c 0x80 identifies child sprites
|
||||
uint8_t size_x = 0;
|
||||
uint8_t size_y = 0;
|
||||
uint8_t size_z = 0;
|
||||
PalSpriteID image{};
|
||||
struct DrawTileSeqStruct : SpriteBounds {
|
||||
PalSpriteID image;
|
||||
|
||||
constexpr DrawTileSeqStruct() = default;
|
||||
constexpr DrawTileSeqStruct(int8_t origin_x, int8_t origin_y, int8_t origin_z, uint8_t extent_x, uint8_t extent_y, uint8_t extent_z, PalSpriteID image) :
|
||||
SpriteBounds({origin_x, origin_y, origin_z}, {extent_x, extent_y, extent_z}, {}), image(image) {}
|
||||
|
||||
/** Check whether this is a parent sprite with a boundingbox. */
|
||||
bool IsParentSprite() const
|
||||
inline bool IsParentSprite() const
|
||||
{
|
||||
return (uint8_t)this->delta_z != 0x80;
|
||||
return static_cast<uint8_t>(this->origin.z) != 0x80;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -69,14 +78,9 @@ struct DrawTileSpriteSpan : DrawTileSprites {
|
|||
* This structure is the same for both Industries and Houses.
|
||||
* Buildings here reference a general type of construction
|
||||
*/
|
||||
struct DrawBuildingsTileStruct {
|
||||
struct DrawBuildingsTileStruct : SpriteBounds {
|
||||
PalSpriteID ground;
|
||||
PalSpriteID building;
|
||||
uint8_t subtile_x;
|
||||
uint8_t subtile_y;
|
||||
uint8_t width;
|
||||
uint8_t height;
|
||||
uint8_t dz;
|
||||
uint8_t draw_proc; // this allows to specify a special drawing procedure.
|
||||
};
|
||||
|
||||
|
|
|
@ -3184,7 +3184,7 @@ static void DrawTile_Station(TileInfo *ti)
|
|||
7, 8, 9 // SLOPE_NE, SLOPE_ENW, SLOPE_SEN
|
||||
};
|
||||
|
||||
AddSortableSpriteToDraw(image + foundation_parts[ti->tileh], PAL_NONE, ti->x, ti->y, 16, 16, 7, ti->z);
|
||||
AddSortableSpriteToDraw(image + foundation_parts[ti->tileh], PAL_NONE, *ti, {{}, {TILE_SIZE, TILE_SIZE, 7}, {}});
|
||||
} else {
|
||||
/* Draw simple foundations, built up from 8 possible foundation sprites. */
|
||||
|
||||
|
@ -3218,7 +3218,7 @@ static void DrawTile_Station(TileInfo *ti)
|
|||
StartSpriteCombine();
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (HasBit(parts, i)) {
|
||||
AddSortableSpriteToDraw(image + i, PAL_NONE, ti->x, ti->y, 16, 16, 7, ti->z);
|
||||
AddSortableSpriteToDraw(image + i, PAL_NONE, *ti, {{}, {TILE_SIZE, TILE_SIZE, 7}, {}});
|
||||
}
|
||||
}
|
||||
EndSpriteCombine();
|
||||
|
|
|
@ -313,14 +313,12 @@ enum WireSpriteOffset : uint8_t {
|
|||
WSO_ENTRANCE_SE,
|
||||
};
|
||||
|
||||
struct SortableSpriteStruct {
|
||||
struct SortableSpriteStruct : SpriteBounds {
|
||||
uint8_t image_offset;
|
||||
int8_t x_offset;
|
||||
int8_t y_offset;
|
||||
int8_t x_size;
|
||||
int8_t y_size;
|
||||
int8_t z_size;
|
||||
int8_t z_offset;
|
||||
|
||||
constexpr SortableSpriteStruct(uint8_t image_offset, const SpriteBounds &bounds) : SpriteBounds(bounds), image_offset(image_offset) {}
|
||||
constexpr SortableSpriteStruct(uint8_t image_offset, int8_t x_offset, int8_t y_offset, uint8_t x_size, uint8_t y_size, uint8_t z_size, int8_t z_offset) :
|
||||
SpriteBounds({x_offset, y_offset, z_offset}, {x_size, y_size, z_size}, {}), image_offset(image_offset) {}
|
||||
};
|
||||
|
||||
/** Distance between wire and rail */
|
||||
|
@ -398,11 +396,17 @@ static const SortableSpriteStruct _rail_catenary_sprite_data_depot[] = {
|
|||
{ WSO_ENTRANCE_NW, 7, 0, 1, 15, 1, ELRAIL_ELEVATION } //! Wire for NW depot exit
|
||||
};
|
||||
|
||||
/**
|
||||
* In tunnelheads, the bounding box for wires covers nearly the full tile, and is lowered a bit.
|
||||
* ELRAIL_TUNNEL_OFFSET is the difference between visual position and bounding box.
|
||||
*/
|
||||
static const int8_t ELRAIL_TUNNEL_OFFSET = ELRAIL_ELEVATION - BB_Z_SEPARATOR;
|
||||
|
||||
static const SortableSpriteStruct _rail_catenary_sprite_data_tunnel[] = {
|
||||
{ WSO_ENTRANCE_SW, 0, 7, 15, 1, 1, ELRAIL_ELEVATION }, //! Wire for NE tunnel (SW facing exit)
|
||||
{ WSO_ENTRANCE_NW, 7, 0, 1, 15, 1, ELRAIL_ELEVATION }, //! Wire for SE tunnel (NW facing exit)
|
||||
{ WSO_ENTRANCE_NE, 0, 7, 15, 1, 1, ELRAIL_ELEVATION }, //! Wire for SW tunnel (NE facing exit)
|
||||
{ WSO_ENTRANCE_SE, 7, 0, 1, 15, 1, ELRAIL_ELEVATION } //! Wire for NW tunnel (SE facing exit)
|
||||
{ WSO_ENTRANCE_SW, {{0, 0, BB_Z_SEPARATOR}, {16, 15, 1}, {0, 7, ELRAIL_TUNNEL_OFFSET}} }, //! Wire for NE tunnel (SW facing exit)
|
||||
{ WSO_ENTRANCE_NW, {{0, 0, BB_Z_SEPARATOR}, {15, 16, 1}, {7, 0, ELRAIL_TUNNEL_OFFSET}} }, //! Wire for SE tunnel (NW facing exit)
|
||||
{ WSO_ENTRANCE_NE, {{0, 0, BB_Z_SEPARATOR}, {16, 15, 1}, {0, 7, ELRAIL_TUNNEL_OFFSET}} }, //! Wire for SW tunnel (NE facing exit)
|
||||
{ WSO_ENTRANCE_SE, {{0, 0, BB_Z_SEPARATOR}, {15, 16, 1}, {7, 0, ELRAIL_TUNNEL_OFFSET}} } //! Wire for NW tunnel (SE facing exit)
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -37,15 +37,15 @@ struct DrawIndustryCoordinates {
|
|||
* @param p1 palette ID of ground sprite
|
||||
* @param s2 sprite ID of building sprite
|
||||
* @param p2 palette ID of building sprite
|
||||
* @param sx coordinate x of the sprite
|
||||
* @param sy coordinate y of the sprite
|
||||
* @param w width of the sprite
|
||||
* @param h height of the sprite
|
||||
* @param dz virtual height of the sprite
|
||||
* @param dx The x-position of the sprite within the tile.
|
||||
* @param dy the y-position of the sprite within the tile.
|
||||
* @param sx the x-extent of the sprite.
|
||||
* @param sy the y-extent of the sprite.
|
||||
* @param sz the z-extent of the sprite.
|
||||
* @param p this allows to specify a special drawing procedure.
|
||||
* @see DrawBuildingsTileStruct
|
||||
*/
|
||||
#define M(s1, p1, s2, p2, sx, sy, w, h, dz, p) { { s1, p1 }, { s2, p2 }, sx, sy, w, h, dz, p }
|
||||
#define M(s1, p1, s2, p2, dx, dy, sx, sy, sz, p) { {{dx, dy, 0}, {sx, sy, sz}, {}}, { s1, p1 }, { s2, p2 }, p}
|
||||
|
||||
/** Structure for industry tiles drawing */
|
||||
static const DrawBuildingsTileStruct _industry_draw_tile_data[NEW_INDUSTRYTILEOFFSET * 4] = {
|
||||
|
|
|
@ -13,15 +13,15 @@
|
|||
* @param p1 The first sprite's palette of the building, mostly the ground sprite
|
||||
* @param s2 The second sprite of the building.
|
||||
* @param p2 The second sprite's palette of the building.
|
||||
* @param sx The x-position of the sprite within the tile
|
||||
* @param sy the y-position of the sprite within the tile
|
||||
* @param w the width of the sprite
|
||||
* @param h the height of the sprite
|
||||
* @param dz the virtual height of the sprite
|
||||
* @param dx The x-position of the sprite within the tile.
|
||||
* @param dy the y-position of the sprite within the tile.
|
||||
* @param sx the x-extent of the sprite.
|
||||
* @param sy the y-extent of the sprite.
|
||||
* @param sz the z-extent of the sprite.
|
||||
* @param p set to 1 if a lift is present ()
|
||||
* @see DrawBuildingsTileStruct
|
||||
*/
|
||||
#define M(s1, p1, s2, p2, sx, sy, w, h, dz, p) { { s1, p1 }, { s2, p2 }, sx, sy, w, h, dz, p}
|
||||
#define M(s1, p1, s2, p2, dx, dy, sx, sy, sz, p) { {{dx, dy, 0}, {sx, sy, sz}, {}}, { s1, p1 }, { s2, p2 }, p}
|
||||
|
||||
/** structure of houses graphics*/
|
||||
static const DrawBuildingsTileStruct _town_draw_tile_data[] = {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#ifndef TILE_CMD_H
|
||||
#define TILE_CMD_H
|
||||
|
||||
#include "core/geometry_type.hpp"
|
||||
#include "command_type.h"
|
||||
#include "vehicle_type.h"
|
||||
#include "cargo_type.h"
|
||||
|
@ -26,12 +27,9 @@ enum class VehicleEnterTileState : uint8_t {
|
|||
using VehicleEnterTileStates = EnumBitSet<VehicleEnterTileState, uint8_t>;
|
||||
|
||||
/** Tile information, used while rendering the tile */
|
||||
struct TileInfo {
|
||||
int x; ///< X position of the tile in unit coordinates
|
||||
int y; ///< Y position of the tile in unit coordinates
|
||||
struct TileInfo : Coord3D<int> {
|
||||
Slope tileh; ///< Slope of the tile
|
||||
TileIndex tile; ///< Tile index
|
||||
int z; ///< Height
|
||||
};
|
||||
|
||||
/** Tile description for the 'land area information' tool */
|
||||
|
|
|
@ -12,29 +12,29 @@
|
|||
|
||||
#include "core/strong_typedef_type.hpp"
|
||||
|
||||
static const uint TILE_SIZE = 16; ///< Tile size in world coordinates.
|
||||
static const uint TILE_UNIT_MASK = TILE_SIZE - 1; ///< For masking in/out the inner-tile world coordinate units.
|
||||
static const uint TILE_PIXELS = 32; ///< Pixel distance between tile columns/rows in #ZOOM_BASE.
|
||||
static const uint TILE_HEIGHT = 8; ///< Height of a height level in world coordinate AND in pixels in #ZOOM_BASE.
|
||||
static constexpr uint TILE_SIZE = 16; ///< Tile size in world coordinates.
|
||||
static constexpr uint TILE_UNIT_MASK = TILE_SIZE - 1; ///< For masking in/out the inner-tile world coordinate units.
|
||||
static constexpr uint TILE_PIXELS = 32; ///< Pixel distance between tile columns/rows in #ZOOM_BASE.
|
||||
static constexpr uint TILE_HEIGHT = 8; ///< Height of a height level in world coordinate AND in pixels in #ZOOM_BASE.
|
||||
|
||||
static const uint MAX_BUILDING_PIXELS = 200; ///< Maximum height of a building in pixels in #ZOOM_BASE. (Also applies to "bridge buildings" on the bridge floor.)
|
||||
static const int MAX_VEHICLE_PIXEL_X = 192; ///< Maximum width of a vehicle in pixels in #ZOOM_BASE.
|
||||
static const int MAX_VEHICLE_PIXEL_Y = 96; ///< Maximum height of a vehicle in pixels in #ZOOM_BASE.
|
||||
static constexpr uint MAX_BUILDING_PIXELS = 200; ///< Maximum height of a building in pixels in #ZOOM_BASE. (Also applies to "bridge buildings" on the bridge floor.)
|
||||
static constexpr int MAX_VEHICLE_PIXEL_X = 192; ///< Maximum width of a vehicle in pixels in #ZOOM_BASE.
|
||||
static constexpr int MAX_VEHICLE_PIXEL_Y = 96; ///< Maximum height of a vehicle in pixels in #ZOOM_BASE.
|
||||
|
||||
static const uint MAX_TILE_HEIGHT = 255; ///< Maximum allowed tile height
|
||||
static constexpr uint MAX_TILE_HEIGHT = 255; ///< Maximum allowed tile height
|
||||
|
||||
static const uint MIN_HEIGHTMAP_HEIGHT = 1; ///< Lowest possible peak value for heightmap creation
|
||||
static const uint MIN_CUSTOM_TERRAIN_TYPE = 1; ///< Lowest possible peak value for world generation
|
||||
static constexpr uint MIN_HEIGHTMAP_HEIGHT = 1; ///< Lowest possible peak value for heightmap creation
|
||||
static constexpr uint MIN_CUSTOM_TERRAIN_TYPE = 1; ///< Lowest possible peak value for world generation
|
||||
|
||||
static const uint MIN_MAP_HEIGHT_LIMIT = 15; ///< Lower bound of maximum allowed heightlevel (in the construction settings)
|
||||
static const uint MAX_MAP_HEIGHT_LIMIT = MAX_TILE_HEIGHT; ///< Upper bound of maximum allowed heightlevel (in the construction settings)
|
||||
static constexpr uint MIN_MAP_HEIGHT_LIMIT = 15; ///< Lower bound of maximum allowed heightlevel (in the construction settings)
|
||||
static constexpr uint MAX_MAP_HEIGHT_LIMIT = MAX_TILE_HEIGHT; ///< Upper bound of maximum allowed heightlevel (in the construction settings)
|
||||
|
||||
static const uint MIN_SNOWLINE_HEIGHT = 2; ///< Minimum snowline height
|
||||
static const uint DEF_SNOWLINE_HEIGHT = 10; ///< Default snowline height
|
||||
static const uint MAX_SNOWLINE_HEIGHT = (MAX_TILE_HEIGHT - 2); ///< Maximum allowed snowline height
|
||||
static constexpr uint MIN_SNOWLINE_HEIGHT = 2; ///< Minimum snowline height
|
||||
static constexpr uint DEF_SNOWLINE_HEIGHT = 10; ///< Default snowline height
|
||||
static constexpr uint MAX_SNOWLINE_HEIGHT = (MAX_TILE_HEIGHT - 2); ///< Maximum allowed snowline height
|
||||
|
||||
static const uint DEF_SNOW_COVERAGE = 40; ///< Default snow coverage.
|
||||
static const uint DEF_DESERT_COVERAGE = 50; ///< Default desert coverage.
|
||||
static constexpr uint DEF_SNOW_COVERAGE = 40; ///< Default snow coverage.
|
||||
static constexpr uint DEF_DESERT_COVERAGE = 50; ///< Default desert coverage.
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -284,15 +284,7 @@ static void DrawTile_Town(TileInfo *ti)
|
|||
/* Add a house on top of the ground? */
|
||||
SpriteID image = dcts->building.sprite;
|
||||
if (image != 0) {
|
||||
AddSortableSpriteToDraw(image, dcts->building.pal,
|
||||
ti->x + dcts->subtile_x,
|
||||
ti->y + dcts->subtile_y,
|
||||
dcts->width,
|
||||
dcts->height,
|
||||
dcts->dz,
|
||||
ti->z,
|
||||
IsTransparencySet(TO_HOUSES)
|
||||
);
|
||||
AddSortableSpriteToDraw(image, dcts->building.pal, *ti, *dcts, IsTransparencySet(TO_HOUSES));
|
||||
|
||||
if (IsTransparencySet(TO_HOUSES)) return;
|
||||
}
|
||||
|
|
|
@ -1376,7 +1376,7 @@ void DrawHouseInGUI(int x, int y, HouseID house_id, int view)
|
|||
|
||||
/* Add a house on top of the ground? */
|
||||
if (dcts.building.sprite != 0) {
|
||||
Point pt = RemapCoords(dcts.subtile_x, dcts.subtile_y, 0);
|
||||
Point pt = RemapCoords(dcts.origin.x, dcts.origin.y, dcts.origin.z);
|
||||
DrawSprite(dcts.building.sprite, dcts.building.pal, x + UnScaleGUI(pt.x), y + UnScaleGUI(pt.y));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1491,13 +1491,7 @@ CommandCost CmdSellRailWagon(DoCommandFlags flags, Vehicle *t, bool sell_chain,
|
|||
void Train::UpdateDeltaXY()
|
||||
{
|
||||
/* Set common defaults. */
|
||||
this->x_offs = -1;
|
||||
this->y_offs = -1;
|
||||
this->x_extent = 3;
|
||||
this->y_extent = 3;
|
||||
this->z_extent = 6;
|
||||
this->x_bb_offs = 0;
|
||||
this->y_bb_offs = 0;
|
||||
this->bounds = {{-1, -1, 0}, {3, 3, 6}, {}};
|
||||
|
||||
/* Set if flipped and engine is NOT flagged with custom flip handling. */
|
||||
int flipped = this->flags.Test(VehicleRailFlag::Flipped) && !EngInfo(this->engine_type)->misc_flags.Test(EngineMiscFlag::RailFlips);
|
||||
|
@ -1508,50 +1502,47 @@ void Train::UpdateDeltaXY()
|
|||
if (flipped) dir = ReverseDir(dir);
|
||||
|
||||
if (!IsDiagonalDirection(dir)) {
|
||||
static const int _sign_table[] =
|
||||
{
|
||||
static const Point _sign_table[] = {
|
||||
/* x, y */
|
||||
-1, -1, // DIR_N
|
||||
-1, 1, // DIR_E
|
||||
1, 1, // DIR_S
|
||||
1, -1, // DIR_W
|
||||
{-1, -1}, // DIR_N
|
||||
{-1, 1}, // DIR_E
|
||||
{ 1, 1}, // DIR_S
|
||||
{ 1, -1}, // DIR_W
|
||||
};
|
||||
|
||||
int half_shorten = (VEHICLE_LENGTH - this->gcache.cached_veh_length + flipped) / 2;
|
||||
|
||||
/* For all straight directions, move the bound box to the centre of the vehicle, but keep the size. */
|
||||
this->x_offs -= half_shorten * _sign_table[dir];
|
||||
this->y_offs -= half_shorten * _sign_table[dir + 1];
|
||||
this->x_extent += this->x_bb_offs = half_shorten * _sign_table[dir];
|
||||
this->y_extent += this->y_bb_offs = half_shorten * _sign_table[dir + 1];
|
||||
this->bounds.offset.x -= half_shorten * _sign_table[DirToDiagDir(dir)].x;
|
||||
this->bounds.offset.y -= half_shorten * _sign_table[DirToDiagDir(dir)].y;
|
||||
} else {
|
||||
switch (dir) {
|
||||
/* Shorten southern corner of the bounding box according the vehicle length
|
||||
* and center the bounding box on the vehicle. */
|
||||
case DIR_NE:
|
||||
this->x_offs = 1 - (this->gcache.cached_veh_length + 1) / 2 + flip_offs;
|
||||
this->x_extent = this->gcache.cached_veh_length - 1;
|
||||
this->x_bb_offs = -1;
|
||||
this->bounds.origin.x = -(this->gcache.cached_veh_length + 1) / 2 + flip_offs;
|
||||
this->bounds.extent.x = this->gcache.cached_veh_length;
|
||||
this->bounds.offset.x = 1;
|
||||
break;
|
||||
|
||||
case DIR_NW:
|
||||
this->y_offs = 1 - (this->gcache.cached_veh_length + 1) / 2 + flip_offs;
|
||||
this->y_extent = this->gcache.cached_veh_length - 1;
|
||||
this->y_bb_offs = -1;
|
||||
this->bounds.origin.y = -(this->gcache.cached_veh_length + 1) / 2 + flip_offs;
|
||||
this->bounds.extent.y = this->gcache.cached_veh_length;
|
||||
this->bounds.offset.y = 1;
|
||||
break;
|
||||
|
||||
/* Move northern corner of the bounding box down according to vehicle length
|
||||
* and center the bounding box on the vehicle. */
|
||||
case DIR_SW:
|
||||
this->x_offs = 1 + (this->gcache.cached_veh_length + 1) / 2 - VEHICLE_LENGTH - flip_offs;
|
||||
this->x_extent = VEHICLE_LENGTH - 1;
|
||||
this->x_bb_offs = VEHICLE_LENGTH - this->gcache.cached_veh_length - 1;
|
||||
this->bounds.origin.x = -(this->gcache.cached_veh_length) / 2 - flip_offs;
|
||||
this->bounds.extent.x = this->gcache.cached_veh_length;
|
||||
this->bounds.offset.x = 1 - (VEHICLE_LENGTH - this->gcache.cached_veh_length);
|
||||
break;
|
||||
|
||||
case DIR_SE:
|
||||
this->y_offs = 1 + (this->gcache.cached_veh_length + 1) / 2 - VEHICLE_LENGTH - flip_offs;
|
||||
this->y_extent = VEHICLE_LENGTH - 1;
|
||||
this->y_bb_offs = VEHICLE_LENGTH - this->gcache.cached_veh_length - 1;
|
||||
this->bounds.origin.y = -(this->gcache.cached_veh_length) / 2 - flip_offs;
|
||||
this->bounds.extent.y = this->gcache.cached_veh_length;
|
||||
this->bounds.offset.y = 1 - (VEHICLE_LENGTH - this->gcache.cached_veh_length);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
|
@ -635,7 +635,7 @@ CommandCost CmdPlantTree(DoCommandFlags flags, TileIndex tile, TileIndex start_t
|
|||
}
|
||||
|
||||
struct TreeListEnt : PalSpriteID {
|
||||
uint8_t x, y;
|
||||
int8_t x, y;
|
||||
};
|
||||
|
||||
static void DrawTile_Trees(TileInfo *ti)
|
||||
|
@ -699,7 +699,8 @@ static void DrawTile_Trees(TileInfo *ti)
|
|||
}
|
||||
}
|
||||
|
||||
AddSortableSpriteToDraw(te[mi].sprite, te[mi].pal, ti->x + te[mi].x, ti->y + te[mi].y, 16 - te[mi].x, 16 - te[mi].y, 0x30, z, IsTransparencySet(TO_TREES), -te[mi].x, -te[mi].y);
|
||||
SpriteBounds bounds{{}, {TILE_SIZE, TILE_SIZE, 48}, {te[mi].x, te[mi].y, 0}};
|
||||
AddSortableSpriteToDraw(te[mi].sprite, te[mi].pal, ti->x, ti->y, z, bounds, IsTransparencySet(TO_TREES));
|
||||
|
||||
/* replace the removed one with the last one */
|
||||
te[mi] = te[trees - 1];
|
||||
|
|
|
@ -1017,10 +1017,10 @@ static CommandCost ClearTile_TunnelBridge(TileIndex tile, DoCommandFlags flags)
|
|||
* @param h Bounding box size in Y direction
|
||||
* @param subsprite Optional subsprite for drawing halfpillars
|
||||
*/
|
||||
static inline void DrawPillar(const PalSpriteID *psid, int x, int y, int z, int w, int h, const SubSprite *subsprite)
|
||||
static inline void DrawPillar(const PalSpriteID *psid, int x, int y, int z, uint8_t w, uint8_t h, const SubSprite *subsprite)
|
||||
{
|
||||
static const int PILLAR_Z_OFFSET = TILE_HEIGHT - BRIDGE_Z_START; ///< Start offset of pillar wrt. bridge (downwards)
|
||||
AddSortableSpriteToDraw(psid->sprite, psid->pal, x, y, w, h, BB_HEIGHT_UNDER_BRIDGE - PILLAR_Z_OFFSET, z, IsTransparencySet(TO_BRIDGES), 0, 0, -PILLAR_Z_OFFSET, subsprite);
|
||||
AddSortableSpriteToDraw(psid->sprite, psid->pal, x, y, z, {{0, 0, -PILLAR_Z_OFFSET}, {w, h, BB_HEIGHT_UNDER_BRIDGE}, {0, 0, PILLAR_Z_OFFSET}}, IsTransparencySet(TO_BRIDGES), subsprite);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1194,18 +1194,20 @@ static void DrawBridgeRoadBits(TileIndex head_tile, int x, int y, int z, int off
|
|||
}
|
||||
}
|
||||
|
||||
static const uint size_x[6] = { 1, 16, 16, 1, 16, 1 };
|
||||
static const uint size_y[6] = { 16, 1, 1, 16, 1, 16 };
|
||||
static const uint front_bb_offset_x[6] = { 15, 0, 0, 15, 0, 15 };
|
||||
static const uint front_bb_offset_y[6] = { 0, 15, 15, 0, 15, 0 };
|
||||
static constexpr SpriteBounds back_bounds[6] = {
|
||||
{{}, {0, TILE_SIZE, 40}, {}},
|
||||
{{}, {TILE_SIZE, 0, 40}, {}},
|
||||
{{}, {TILE_SIZE, 0, 40}, {}},
|
||||
{{}, {0, TILE_SIZE, 40}, {}},
|
||||
{{}, {TILE_SIZE, 0, 40}, {}},
|
||||
{{}, {0, TILE_SIZE, 40}, {}},
|
||||
};
|
||||
|
||||
/* The sprites under the vehicles are drawn as SpriteCombine. StartSpriteCombine() has already been called
|
||||
* The bounding boxes here are the same as for bridge front/roof */
|
||||
for (uint i = 0; i < lengthof(seq_back); ++i) {
|
||||
if (seq_back[i] != 0) {
|
||||
AddSortableSpriteToDraw(seq_back[i], PAL_NONE,
|
||||
x, y, size_x[offset], size_y[offset], 0x28, z,
|
||||
trans_back[i]);
|
||||
AddSortableSpriteToDraw(seq_back[i], PAL_NONE, x, y, z, back_bounds[offset], trans_back[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1213,12 +1215,18 @@ static void DrawBridgeRoadBits(TileIndex head_tile, int x, int y, int z, int off
|
|||
EndSpriteCombine();
|
||||
StartSpriteCombine();
|
||||
|
||||
static constexpr SpriteBounds front_bounds[6] = {
|
||||
{{15, 0, 0}, {0, TILE_SIZE, 40}, {-15, 0, 0}},
|
||||
{{0, 15, 0}, {TILE_SIZE, 0, 40}, {0, -15, 0}},
|
||||
{{0, 15, 0}, {TILE_SIZE, 0, 40}, {0, -15, 0}},
|
||||
{{15, 0, 0}, {0, TILE_SIZE, 40}, {-15, 0, 0}},
|
||||
{{0, 15, 0}, {TILE_SIZE, 0, 40}, {0, -15, 0}},
|
||||
{{15, 0, 0}, {0, TILE_SIZE, 40}, {-15, 0, 0}},
|
||||
};
|
||||
|
||||
for (uint i = 0; i < lengthof(seq_front); ++i) {
|
||||
if (seq_front[i] != 0) {
|
||||
AddSortableSpriteToDraw(seq_front[i], PAL_NONE,
|
||||
x, y, size_x[offset] + front_bb_offset_x[offset], size_y[offset] + front_bb_offset_y[offset], 0x28, z,
|
||||
trans_front[i],
|
||||
front_bb_offset_x[offset], front_bb_offset_y[offset]);
|
||||
AddSortableSpriteToDraw(seq_front[i], PAL_NONE, x, y, z, front_bounds[offset], trans_front[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1251,15 +1259,35 @@ static void DrawTile_TunnelBridge(TileInfo *ti)
|
|||
*
|
||||
*/
|
||||
|
||||
static const int _tunnel_BB[4][12] = {
|
||||
/* tunnnel-roof | Z-separator | tram-catenary
|
||||
* w h bb_x bb_y| x y w h |bb_x bb_y w h */
|
||||
{ 1, 0, -15, -14, 0, 15, 16, 1, 0, 1, 16, 15 }, // NE
|
||||
{ 0, 1, -14, -15, 15, 0, 1, 16, 1, 0, 15, 16 }, // SE
|
||||
{ 1, 0, -15, -14, 0, 15, 16, 1, 0, 1, 16, 15 }, // SW
|
||||
{ 0, 1, -14, -15, 15, 0, 1, 16, 1, 0, 15, 16 }, // NW
|
||||
/* Tunnel sprites are positioned at 15,15, but the bounding box covers most of the tile. */
|
||||
static constexpr SpriteBounds roof_bounds[DIAGDIR_END] = {
|
||||
{{0, 1, BB_Z_SEPARATOR}, {TILE_SIZE, TILE_SIZE - 1, 1}, {TILE_SIZE - 1, TILE_SIZE - 2, -BB_Z_SEPARATOR}}, // NE
|
||||
{{1, 0, BB_Z_SEPARATOR}, {TILE_SIZE - 1, TILE_SIZE, 1}, {TILE_SIZE - 2, TILE_SIZE - 1, -BB_Z_SEPARATOR}}, // SE
|
||||
{{0, 1, BB_Z_SEPARATOR}, {TILE_SIZE, TILE_SIZE - 1, 1}, {TILE_SIZE - 1, TILE_SIZE - 2, -BB_Z_SEPARATOR}}, // SW
|
||||
{{1, 0, BB_Z_SEPARATOR}, {TILE_SIZE - 1, TILE_SIZE, 1}, {TILE_SIZE - 2, TILE_SIZE - 1, -BB_Z_SEPARATOR}}, // NW
|
||||
};
|
||||
|
||||
/* Catenary sprites are positioned at 0,0, with the same bounding box as above. */
|
||||
static constexpr SpriteBounds catenary_bounds[DIAGDIR_END] = {
|
||||
{{0, 1, BB_Z_SEPARATOR}, {TILE_SIZE, TILE_SIZE - 1, 1}, {0, -1, -BB_Z_SEPARATOR}}, // NE
|
||||
{{1, 0, BB_Z_SEPARATOR}, {TILE_SIZE - 1, TILE_SIZE, 1}, {-1, 0, -BB_Z_SEPARATOR}}, // SE
|
||||
{{0, 1, BB_Z_SEPARATOR}, {TILE_SIZE, TILE_SIZE - 1, 1}, {0, -1, -BB_Z_SEPARATOR}}, // SW
|
||||
{{1, 0, BB_Z_SEPARATOR}, {TILE_SIZE - 1, TILE_SIZE, 1}, {-1, 0, -BB_Z_SEPARATOR}}, // NW
|
||||
};
|
||||
|
||||
static constexpr SpriteBounds rear_sep[DIAGDIR_END] = {
|
||||
{{}, {TILE_SIZE, 1, TILE_HEIGHT}, {}}, // NE
|
||||
{{}, {1, TILE_SIZE, TILE_HEIGHT}, {}}, // SE
|
||||
{{}, {TILE_SIZE, 1, TILE_HEIGHT}, {}}, // SW
|
||||
{{}, {1, TILE_SIZE, TILE_HEIGHT}, {}}, // NW
|
||||
};
|
||||
|
||||
static constexpr SpriteBounds front_sep[DIAGDIR_END] = {
|
||||
{{0, TILE_SIZE - 1, 0}, {TILE_SIZE, 1, TILE_HEIGHT}, {}}, // NE
|
||||
{{TILE_SIZE - 1, 0, 0}, {1, TILE_SIZE, TILE_HEIGHT}, {}}, // SE
|
||||
{{0, TILE_SIZE - 1, 0}, {TILE_SIZE, 1, TILE_HEIGHT}, {}}, // SW
|
||||
{{TILE_SIZE - 1, 0, 0}, {1, TILE_SIZE, TILE_HEIGHT}, {}}, // NW
|
||||
};
|
||||
const int *BB_data = _tunnel_BB[tunnelbridge_direction];
|
||||
|
||||
bool catenary = false;
|
||||
|
||||
|
@ -1332,7 +1360,7 @@ static void DrawTile_TunnelBridge(TileInfo *ti)
|
|||
if (catenary_sprite_base != 0) {
|
||||
catenary = true;
|
||||
StartSpriteCombine();
|
||||
AddSortableSpriteToDraw(catenary_sprite_base + tunnelbridge_direction, PAL_NONE, ti->x, ti->y, BB_data[10], BB_data[11], TILE_HEIGHT, ti->z, IsTransparencySet(TO_CATENARY), BB_data[8], BB_data[9], BB_Z_SEPARATOR);
|
||||
AddSortableSpriteToDraw(catenary_sprite_base + tunnelbridge_direction, PAL_NONE, *ti, catenary_bounds[tunnelbridge_direction], IsTransparencySet(TO_CATENARY));
|
||||
}
|
||||
} else {
|
||||
const RailTypeInfo *rti = GetRailTypeInfo(GetRailType(ti->tile));
|
||||
|
@ -1364,15 +1392,15 @@ static void DrawTile_TunnelBridge(TileInfo *ti)
|
|||
|
||||
if (railtype_overlay != 0 && !catenary) StartSpriteCombine();
|
||||
|
||||
AddSortableSpriteToDraw(image + 1, PAL_NONE, ti->x + TILE_SIZE - 1, ti->y + TILE_SIZE - 1, BB_data[0], BB_data[1], TILE_HEIGHT, ti->z, false, BB_data[2], BB_data[3], BB_Z_SEPARATOR);
|
||||
AddSortableSpriteToDraw(image + 1, PAL_NONE, *ti, roof_bounds[tunnelbridge_direction], false);
|
||||
/* Draw railtype tunnel portal overlay if defined. */
|
||||
if (railtype_overlay != 0) AddSortableSpriteToDraw(railtype_overlay + tunnelbridge_direction, PAL_NONE, ti->x + TILE_SIZE - 1, ti->y + TILE_SIZE - 1, BB_data[0], BB_data[1], TILE_HEIGHT, ti->z, false, BB_data[2], BB_data[3], BB_Z_SEPARATOR);
|
||||
if (railtype_overlay != 0) AddSortableSpriteToDraw(railtype_overlay + tunnelbridge_direction, PAL_NONE, *ti, roof_bounds[tunnelbridge_direction], false);
|
||||
|
||||
if (catenary || railtype_overlay != 0) EndSpriteCombine();
|
||||
|
||||
/* Add helper BB for sprite sorting that separates the tunnel from things beside of it. */
|
||||
AddSortableSpriteToDraw(SPR_EMPTY_BOUNDING_BOX, PAL_NONE, ti->x, ti->y, BB_data[6], BB_data[7], TILE_HEIGHT, ti->z);
|
||||
AddSortableSpriteToDraw(SPR_EMPTY_BOUNDING_BOX, PAL_NONE, ti->x + BB_data[4], ti->y + BB_data[5], BB_data[6], BB_data[7], TILE_HEIGHT, ti->z);
|
||||
AddSortableSpriteToDraw(SPR_EMPTY_BOUNDING_BOX, PAL_NONE, *ti, rear_sep[tunnelbridge_direction]);
|
||||
AddSortableSpriteToDraw(SPR_EMPTY_BOUNDING_BOX, PAL_NONE, *ti, front_sep[tunnelbridge_direction]);
|
||||
|
||||
DrawBridgeMiddle(ti);
|
||||
} else { // IsBridge(ti->tile)
|
||||
|
@ -1423,7 +1451,7 @@ static void DrawTile_TunnelBridge(TileInfo *ti)
|
|||
* it doesn't disappear behind it
|
||||
*/
|
||||
/* Bridge heads are drawn solid no matter how invisibility/transparency is set */
|
||||
AddSortableSpriteToDraw(psid->sprite, psid->pal, ti->x, ti->y, 16, 16, ti->tileh == SLOPE_FLAT ? 0 : 8, ti->z);
|
||||
AddSortableSpriteToDraw(psid->sprite, psid->pal, *ti, {{}, {TILE_SIZE, TILE_SIZE, static_cast<uint8_t>(ti->tileh == SLOPE_FLAT ? 0 : TILE_HEIGHT)}, {}});
|
||||
|
||||
if (transport_type == TRANSPORT_ROAD) {
|
||||
uint offset = tunnelbridge_direction;
|
||||
|
@ -1445,9 +1473,9 @@ static void DrawTile_TunnelBridge(TileInfo *ti)
|
|||
SpriteID surface = GetCustomRailSprite(rti, ti->tile, RTSG_BRIDGE);
|
||||
if (surface != 0) {
|
||||
if (HasBridgeFlatRamp(ti->tileh, DiagDirToAxis(tunnelbridge_direction))) {
|
||||
AddSortableSpriteToDraw(surface + ((DiagDirToAxis(tunnelbridge_direction) == AXIS_X) ? RTBO_X : RTBO_Y), PAL_NONE, ti->x, ti->y, 16, 16, 0, ti->z + 8);
|
||||
AddSortableSpriteToDraw(surface + ((DiagDirToAxis(tunnelbridge_direction) == AXIS_X) ? RTBO_X : RTBO_Y), PAL_NONE, *ti, {{0, 0, TILE_HEIGHT}, {TILE_SIZE, TILE_SIZE, 0}, {}});
|
||||
} else {
|
||||
AddSortableSpriteToDraw(surface + RTBO_SLOPE + tunnelbridge_direction, PAL_NONE, ti->x, ti->y, 16, 16, 8, ti->z);
|
||||
AddSortableSpriteToDraw(surface + RTBO_SLOPE + tunnelbridge_direction, PAL_NONE, *ti, {{}, {TILE_SIZE, TILE_SIZE, TILE_HEIGHT}, {}});
|
||||
}
|
||||
}
|
||||
/* Don't fallback to non-overlay sprite -- the spec states that
|
||||
|
@ -1460,15 +1488,15 @@ static void DrawTile_TunnelBridge(TileInfo *ti)
|
|||
if (rti->UsesOverlay()) {
|
||||
SpriteID overlay = GetCustomRailSprite(rti, ti->tile, RTSG_OVERLAY);
|
||||
if (HasBridgeFlatRamp(ti->tileh, DiagDirToAxis(tunnelbridge_direction))) {
|
||||
AddSortableSpriteToDraw(overlay + RTO_X + DiagDirToAxis(tunnelbridge_direction), PALETTE_CRASH, ti->x, ti->y, 16, 16, 0, ti->z + 8);
|
||||
AddSortableSpriteToDraw(overlay + RTO_X + DiagDirToAxis(tunnelbridge_direction), PALETTE_CRASH, *ti, {{0, 0, TILE_HEIGHT}, {TILE_SIZE, TILE_SIZE, 0}, {}});
|
||||
} else {
|
||||
AddSortableSpriteToDraw(overlay + RTO_SLOPE_NE + tunnelbridge_direction, PALETTE_CRASH, ti->x, ti->y, 16, 16, 8, ti->z);
|
||||
AddSortableSpriteToDraw(overlay + RTO_SLOPE_NE + tunnelbridge_direction, PALETTE_CRASH, *ti, {{}, {TILE_SIZE, TILE_SIZE, TILE_HEIGHT}, {}});
|
||||
}
|
||||
} else {
|
||||
if (HasBridgeFlatRamp(ti->tileh, DiagDirToAxis(tunnelbridge_direction))) {
|
||||
AddSortableSpriteToDraw(DiagDirToAxis(tunnelbridge_direction) == AXIS_X ? rti->base_sprites.single_x : rti->base_sprites.single_y, PALETTE_CRASH, ti->x, ti->y, 16, 16, 0, ti->z + 8);
|
||||
AddSortableSpriteToDraw(DiagDirToAxis(tunnelbridge_direction) == AXIS_X ? rti->base_sprites.single_x : rti->base_sprites.single_y, PALETTE_CRASH, *ti, {{0, 0, TILE_HEIGHT}, {TILE_SIZE, TILE_SIZE, 0}, {}});
|
||||
} else {
|
||||
AddSortableSpriteToDraw(rti->base_sprites.single_sloped + tunnelbridge_direction, PALETTE_CRASH, ti->x, ti->y, 16, 16, 8, ti->z);
|
||||
AddSortableSpriteToDraw(rti->base_sprites.single_sloped + tunnelbridge_direction, PALETTE_CRASH, *ti, {{}, {TILE_SIZE, TILE_SIZE, TILE_HEIGHT}, {}});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1578,7 +1606,7 @@ void DrawBridgeMiddle(const TileInfo *ti)
|
|||
int z = bridge_z - BRIDGE_Z_START;
|
||||
|
||||
/* Add a bounding box that separates the bridge from things below it. */
|
||||
AddSortableSpriteToDraw(SPR_EMPTY_BOUNDING_BOX, PAL_NONE, x, y, 16, 16, 1, bridge_z - TILE_HEIGHT + BB_Z_SEPARATOR);
|
||||
AddSortableSpriteToDraw(SPR_EMPTY_BOUNDING_BOX, PAL_NONE, x, y, bridge_z - TILE_HEIGHT + BB_Z_SEPARATOR, {{}, {TILE_SIZE, TILE_SIZE, 1}, {}});
|
||||
|
||||
/* Draw Trambits as SpriteCombine */
|
||||
if (transport_type == TRANSPORT_ROAD || transport_type == TRANSPORT_RAIL) StartSpriteCombine();
|
||||
|
@ -1586,9 +1614,9 @@ void DrawBridgeMiddle(const TileInfo *ti)
|
|||
/* Draw floor and far part of bridge*/
|
||||
if (!IsInvisibilitySet(TO_BRIDGES)) {
|
||||
if (axis == AXIS_X) {
|
||||
AddSortableSpriteToDraw(psid->sprite, psid->pal, x, y, 16, 1, 0x28, z, IsTransparencySet(TO_BRIDGES), 0, 0, BRIDGE_Z_START);
|
||||
AddSortableSpriteToDraw(psid->sprite, psid->pal, x, y, z, {{0, 0, BRIDGE_Z_START}, {TILE_SIZE, 1, 40}, {0, 0, -BRIDGE_Z_START}}, IsTransparencySet(TO_BRIDGES));
|
||||
} else {
|
||||
AddSortableSpriteToDraw(psid->sprite, psid->pal, x, y, 1, 16, 0x28, z, IsTransparencySet(TO_BRIDGES), 0, 0, BRIDGE_Z_START);
|
||||
AddSortableSpriteToDraw(psid->sprite, psid->pal, x, y, z, {{0, 0, BRIDGE_Z_START}, {1, TILE_SIZE, 40}, {0, 0, -BRIDGE_Z_START}}, IsTransparencySet(TO_BRIDGES));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1602,16 +1630,16 @@ void DrawBridgeMiddle(const TileInfo *ti)
|
|||
if (rti->UsesOverlay() && !IsInvisibilitySet(TO_BRIDGES)) {
|
||||
SpriteID surface = GetCustomRailSprite(rti, rampsouth, RTSG_BRIDGE, TCX_ON_BRIDGE);
|
||||
if (surface != 0) {
|
||||
AddSortableSpriteToDraw(surface + axis, PAL_NONE, x, y, 16, 16, 0, bridge_z, IsTransparencySet(TO_BRIDGES));
|
||||
AddSortableSpriteToDraw(surface + axis, PAL_NONE, x, y, bridge_z, {{}, {TILE_SIZE, TILE_SIZE, 0}, {}}, IsTransparencySet(TO_BRIDGES));
|
||||
}
|
||||
}
|
||||
|
||||
if (_game_mode != GM_MENU && _settings_client.gui.show_track_reservation && !IsInvisibilitySet(TO_BRIDGES) && HasTunnelBridgeReservation(rampnorth)) {
|
||||
if (rti->UsesOverlay()) {
|
||||
SpriteID overlay = GetCustomRailSprite(rti, ti->tile, RTSG_OVERLAY);
|
||||
AddSortableSpriteToDraw(overlay + RTO_X + axis, PALETTE_CRASH, ti->x, ti->y, 16, 16, 0, bridge_z, IsTransparencySet(TO_BRIDGES));
|
||||
AddSortableSpriteToDraw(overlay + RTO_X + axis, PALETTE_CRASH, ti->x, ti->y, bridge_z, {{}, {TILE_SIZE, TILE_SIZE, 0}, {}}, IsTransparencySet(TO_BRIDGES));
|
||||
} else {
|
||||
AddSortableSpriteToDraw(axis == AXIS_X ? rti->base_sprites.single_x : rti->base_sprites.single_y, PALETTE_CRASH, ti->x, ti->y, 16, 16, 0, bridge_z, IsTransparencySet(TO_BRIDGES));
|
||||
AddSortableSpriteToDraw(axis == AXIS_X ? rti->base_sprites.single_x : rti->base_sprites.single_y, PALETTE_CRASH, ti->x, ti->y, bridge_z, {{}, {TILE_SIZE, TILE_SIZE, 0}, {}}, IsTransparencySet(TO_BRIDGES));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1626,10 +1654,10 @@ void DrawBridgeMiddle(const TileInfo *ti)
|
|||
if (!IsInvisibilitySet(TO_BRIDGES)) {
|
||||
if (axis == AXIS_X) {
|
||||
y += 12;
|
||||
if (psid->sprite & SPRITE_MASK) AddSortableSpriteToDraw(psid->sprite, psid->pal, x, y, 16, 4, 0x28, z, IsTransparencySet(TO_BRIDGES), 0, 3, BRIDGE_Z_START);
|
||||
if (psid->sprite & SPRITE_MASK) AddSortableSpriteToDraw(psid->sprite, psid->pal, x, y, z, {{0, 3, BRIDGE_Z_START}, {TILE_SIZE, 1, 40}, {0, -3, -BRIDGE_Z_START}}, IsTransparencySet(TO_BRIDGES));
|
||||
} else {
|
||||
x += 12;
|
||||
if (psid->sprite & SPRITE_MASK) AddSortableSpriteToDraw(psid->sprite, psid->pal, x, y, 4, 16, 0x28, z, IsTransparencySet(TO_BRIDGES), 3, 0, BRIDGE_Z_START);
|
||||
if (psid->sprite & SPRITE_MASK) AddSortableSpriteToDraw(psid->sprite, psid->pal, x, y, z, {{3, 0, BRIDGE_Z_START}, {1, TILE_SIZE, 40}, {-3, 0, -BRIDGE_Z_START}}, IsTransparencySet(TO_BRIDGES));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1109,8 +1109,7 @@ static void DoDrawVehicle(const Vehicle *v)
|
|||
for (uint i = 0; i < v->sprite_cache.sprite_seq.count; ++i) {
|
||||
PaletteID pal2 = v->sprite_cache.sprite_seq.seq[i].pal;
|
||||
if (!pal2 || v->vehstatus.Test(VehState::Crashed)) pal2 = pal;
|
||||
AddSortableSpriteToDraw(v->sprite_cache.sprite_seq.seq[i].sprite, pal2, v->x_pos + v->x_offs, v->y_pos + v->y_offs,
|
||||
v->x_extent, v->y_extent, v->z_extent, v->z_pos, shadowed, v->x_bb_offs, v->y_bb_offs);
|
||||
AddSortableSpriteToDraw(v->sprite_cache.sprite_seq.seq[i].sprite, pal2, v->x_pos, v->y_pos, v->z_pos, v->bounds, shadowed);
|
||||
}
|
||||
EndSpriteCombine();
|
||||
}
|
||||
|
@ -1674,12 +1673,24 @@ void Vehicle::UpdateBoundingBoxCoordinates(bool update_cache) const
|
|||
Rect new_coord;
|
||||
this->sprite_cache.sprite_seq.GetBounds(&new_coord);
|
||||
|
||||
Point pt = RemapCoords(this->x_pos + this->x_offs, this->y_pos + this->y_offs, this->z_pos);
|
||||
/* z-bounds are not used. */
|
||||
Point pt = RemapCoords(this->x_pos + this->bounds.origin.x + this->bounds.offset.x, this->y_pos + this->bounds.origin.y + this->bounds.offset.y, this->z_pos);
|
||||
new_coord.left += pt.x;
|
||||
new_coord.top += pt.y;
|
||||
new_coord.right += pt.x + 2 * ZOOM_BASE;
|
||||
new_coord.bottom += pt.y + 2 * ZOOM_BASE;
|
||||
|
||||
extern bool _draw_bounding_boxes;
|
||||
if (_draw_bounding_boxes) {
|
||||
int x = this->x_pos + this->bounds.origin.x;
|
||||
int y = this->y_pos + this->bounds.origin.y;
|
||||
int z = this->z_pos + this->bounds.origin.z;
|
||||
new_coord.left = std::min(new_coord.left, RemapCoords(x + bounds.extent.x, y, z).x);
|
||||
new_coord.right = std::max(new_coord.right, RemapCoords(x, y + bounds.extent.y, z).x + 1);
|
||||
new_coord.top = std::min(new_coord.top, RemapCoords(x, y, z + bounds.extent.z).y);
|
||||
new_coord.bottom = std::max(new_coord.bottom, RemapCoords(x + bounds.extent.x, y + bounds.extent.y, z).y + 1);
|
||||
}
|
||||
|
||||
if (update_cache) {
|
||||
/*
|
||||
* If the old coordinates are invalid, set the cache to the new coordinates for correct
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#ifndef VEHICLE_BASE_H
|
||||
#define VEHICLE_BASE_H
|
||||
|
||||
#include "sprite.h"
|
||||
#include "track_type.h"
|
||||
#include "command_type.h"
|
||||
#include "order_base.h"
|
||||
|
@ -293,13 +294,7 @@ public:
|
|||
* 0xff == reserved for another custom sprite
|
||||
*/
|
||||
uint8_t spritenum = 0;
|
||||
uint8_t x_extent = 0; ///< x-extent of vehicle bounding box
|
||||
uint8_t y_extent = 0; ///< y-extent of vehicle bounding box
|
||||
uint8_t z_extent = 0; ///< z-extent of vehicle bounding box
|
||||
int8_t x_bb_offs = 0; ///< x offset of vehicle bounding box
|
||||
int8_t y_bb_offs = 0; ///< y offset of vehicle bounding box
|
||||
int8_t x_offs = 0; ///< x offset for vehicle sprite
|
||||
int8_t y_offs = 0; ///< y offset for vehicle sprite
|
||||
SpriteBounds bounds{}; ///< Bounding box of vehicle.
|
||||
EngineID engine_type = EngineID::Invalid(); ///< The type of engine used for this vehicle.
|
||||
|
||||
TextEffectID fill_percent_te_id = INVALID_TE_ID; ///< a text-effect id to a loading indicator object
|
||||
|
|
|
@ -660,12 +660,17 @@ static void AddCombinedSprite(SpriteID image, PaletteID pal, int x, int y, int z
|
|||
* @param bb_offset_z bounding box extent towards negative Z (world)
|
||||
* @param sub Only draw a part of the sprite.
|
||||
*/
|
||||
void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int w, int h, int dz, int z, bool transparent, int bb_offset_x, int bb_offset_y, int bb_offset_z, const SubSprite *sub)
|
||||
void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int z, const SpriteBounds &bounds, bool transparent, const SubSprite *sub)
|
||||
{
|
||||
int32_t left, right, top, bottom;
|
||||
|
||||
assert((image & SPRITE_MASK) < MAX_SPRITES);
|
||||
|
||||
/* Move to bounding box. */
|
||||
x += bounds.origin.x;
|
||||
y += bounds.origin.y;
|
||||
z += bounds.origin.z;
|
||||
|
||||
/* make the sprites transparent with the right palette */
|
||||
if (transparent) {
|
||||
SetBit(image, PALETTE_MODIFIER_TRANSPARENT);
|
||||
|
@ -673,21 +678,21 @@ void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int w,
|
|||
}
|
||||
|
||||
if (_vd.combine_sprites == SPRITE_COMBINE_ACTIVE) {
|
||||
AddCombinedSprite(image, pal, x, y, z, sub);
|
||||
AddCombinedSprite(image, pal, x + bounds.offset.x, y + bounds.offset.y, z + bounds.offset.z, sub);
|
||||
return;
|
||||
}
|
||||
|
||||
_vd.last_child = LAST_CHILD_NONE;
|
||||
|
||||
Point pt = RemapCoords(x, y, z);
|
||||
Point pt = RemapCoords(x + bounds.offset.x, y + bounds.offset.y, z + bounds.offset.z);
|
||||
int tmp_left, tmp_top, tmp_x = pt.x, tmp_y = pt.y;
|
||||
|
||||
/* Compute screen extents of sprite */
|
||||
if (image == SPR_EMPTY_BOUNDING_BOX) {
|
||||
left = tmp_left = RemapCoords(x + w , y + bb_offset_y, z + bb_offset_z).x;
|
||||
right = RemapCoords(x + bb_offset_x, y + h , z + bb_offset_z).x + 1;
|
||||
top = tmp_top = RemapCoords(x + bb_offset_x, y + bb_offset_y, z + dz ).y;
|
||||
bottom = RemapCoords(x + w , y + h , z + bb_offset_z).y + 1;
|
||||
left = tmp_left = RemapCoords(x + bounds.extent.x, y, z).x;
|
||||
right = RemapCoords(x, y + bounds.extent.y, z).x + 1;
|
||||
top = tmp_top = RemapCoords(x, y, z + bounds.extent.z).y;
|
||||
bottom = RemapCoords(x + bounds.extent.x, y + bounds.extent.y, z).y + 1;
|
||||
} else {
|
||||
const Sprite *spr = GetSprite(image & SPRITE_MASK, SpriteType::Normal);
|
||||
left = tmp_left = (pt.x += spr->x_offs);
|
||||
|
@ -698,10 +703,10 @@ void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int w,
|
|||
|
||||
if (_draw_bounding_boxes && (image != SPR_EMPTY_BOUNDING_BOX)) {
|
||||
/* Compute maximal extents of sprite and its bounding box */
|
||||
left = std::min(left , RemapCoords(x + w , y + bb_offset_y, z + bb_offset_z).x);
|
||||
right = std::max(right , RemapCoords(x + bb_offset_x, y + h , z + bb_offset_z).x + 1);
|
||||
top = std::min(top , RemapCoords(x + bb_offset_x, y + bb_offset_y, z + dz ).y);
|
||||
bottom = std::max(bottom, RemapCoords(x + w , y + h , z + bb_offset_z).y + 1);
|
||||
left = std::min(left , RemapCoords(x + bounds.extent.x, y, z).x);
|
||||
right = std::max(right , RemapCoords(x, y + bounds.extent.y, z).x + 1);
|
||||
top = std::min(top , RemapCoords(x, y, z + bounds.extent.z).y);
|
||||
bottom = std::max(bottom, RemapCoords(x + bounds.extent.x, y + bounds.extent.y, z).y + 1);
|
||||
}
|
||||
|
||||
/* Do not add the sprite to the viewport, if it is outside */
|
||||
|
@ -722,14 +727,14 @@ void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int w,
|
|||
ps.image = image;
|
||||
ps.pal = pal;
|
||||
ps.sub = sub;
|
||||
ps.xmin = x + bb_offset_x;
|
||||
ps.xmax = x + std::max(bb_offset_x, w) - 1;
|
||||
ps.xmin = x;
|
||||
ps.xmax = x + bounds.extent.x - 1;
|
||||
|
||||
ps.ymin = y + bb_offset_y;
|
||||
ps.ymax = y + std::max(bb_offset_y, h) - 1;
|
||||
ps.ymin = y;
|
||||
ps.ymax = y + bounds.extent.y - 1;
|
||||
|
||||
ps.zmin = z + bb_offset_z;
|
||||
ps.zmax = z + std::max(bb_offset_z, dz) - 1;
|
||||
ps.zmin = z;
|
||||
ps.zmax = z + bounds.extent.z - 1;
|
||||
|
||||
ps.first_child = LAST_CHILD_NONE;
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#define VIEWPORT_FUNC_H
|
||||
|
||||
#include "gfx_type.h"
|
||||
#include "sprite.h"
|
||||
#include "viewport_type.h"
|
||||
#include "window_type.h"
|
||||
#include "tile_map.h"
|
||||
|
@ -51,10 +52,14 @@ void OffsetGroundSprite(int x, int y);
|
|||
|
||||
void DrawGroundSprite(SpriteID image, PaletteID pal, const SubSprite *sub = nullptr, int extra_offs_x = 0, int extra_offs_y = 0);
|
||||
void DrawGroundSpriteAt(SpriteID image, PaletteID pal, int32_t x, int32_t y, int z, const SubSprite *sub = nullptr, int extra_offs_x = 0, int extra_offs_y = 0);
|
||||
void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int w, int h, int dz, int z, bool transparent = false, int bb_offset_x = 0, int bb_offset_y = 0, int bb_offset_z = 0, const SubSprite *sub = nullptr);
|
||||
void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, int x, int y, int z, const SpriteBounds &bounds, bool transparent = false, const SubSprite *sub = nullptr);
|
||||
void AddChildSpriteScreen(SpriteID image, PaletteID pal, int x, int y, bool transparent = false, const SubSprite *sub = nullptr, bool scale = true, bool relative = true);
|
||||
std::string *ViewportAddString(const DrawPixelInfo *dpi, const ViewportSign *sign, ViewportStringFlags flags, Colours colour);
|
||||
|
||||
inline void AddSortableSpriteToDraw(SpriteID image, PaletteID pal, const Coord3D<int32_t> &world, const SpriteBounds &bounds, bool transparent = false, const SubSprite *sub = nullptr)
|
||||
{
|
||||
AddSortableSpriteToDraw(image, pal, world.x, world.y, world.z, bounds, transparent, sub);
|
||||
}
|
||||
|
||||
void StartSpriteCombine();
|
||||
void EndSpriteCombine();
|
||||
|
|
|
@ -89,8 +89,8 @@ enum ZoomStateChange : uint8_t {
|
|||
* z=6 reserved, currently unused.
|
||||
* z=7 Z separator between bridge/tunnel and the things under/above it.
|
||||
*/
|
||||
static const uint BB_HEIGHT_UNDER_BRIDGE = 6; ///< Everything that can be built under low bridges, must not exceed this Z height.
|
||||
static const uint BB_Z_SEPARATOR = 7; ///< Separates the bridge/tunnel from the things under/above it.
|
||||
static constexpr int BB_HEIGHT_UNDER_BRIDGE = 6; ///< Everything that can be built under low bridges, must not exceed this Z height.
|
||||
static constexpr int BB_Z_SEPARATOR = 7; ///< Separates the bridge/tunnel from the things under/above it.
|
||||
|
||||
/** Viewport place method (type of highlighted area and placed objects) */
|
||||
enum ViewportPlaceMethod : uint8_t {
|
||||
|
|
|
@ -806,11 +806,7 @@ static void DrawWaterTileStruct(const TileInfo *ti, std::span<const DrawTileSeqS
|
|||
for (const DrawTileSeqStruct &dtss : seq) {
|
||||
uint tile_offs = offset + dtss.image.sprite;
|
||||
if (feature < CF_END) tile_offs = GetCanalSpriteOffset(feature, ti->tile, tile_offs);
|
||||
AddSortableSpriteToDraw(base + tile_offs, palette,
|
||||
ti->x + dtss.delta_x, ti->y + dtss.delta_y,
|
||||
dtss.size_x, dtss.size_y,
|
||||
dtss.size_z, ti->z + dtss.delta_z,
|
||||
IsTransparencySet(TO_BUILDINGS));
|
||||
AddSortableSpriteToDraw(base + tile_offs, palette, *ti, dtss, IsTransparencySet(TO_BUILDINGS));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue