2019-11-20 16:21:32 +00:00
//-------------------------------------------------------------------------
/*
Copyright ( C ) 2010 - 2019 EDuke32 developers and contributors
Copyright ( C ) 2019 sirlemonhead , Nuke . YKT
This file is part of PCExhumed .
PCExhumed is free software ; you can redistribute it and / or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation .
This program 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 this program ; if not , write to the Free Software
Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 , USA .
*/
//-------------------------------------------------------------------------
2019-11-22 23:11:37 +00:00
# include "ns.h"
2019-08-26 03:59:14 +00:00
# include "engine.h"
# include "exhumed.h"
# include "names.h"
# include <cstdio>
# include <cstring>
2020-01-02 18:56:57 +00:00
# include "c_bind.h"
2019-12-05 21:18:34 +00:00
# include "sound.h"
2019-12-31 18:25:49 +00:00
# include "v_2ddrawer.h"
2020-04-12 05:46:53 +00:00
# include "animtexture.h"
2020-08-20 15:52:56 +00:00
# include "s_music.h"
2020-08-20 19:05:14 +00:00
# include "screenjob.h"
# include "v_draw.h"
2021-04-30 14:21:37 +00:00
# include "vm.h"
2019-08-26 03:59:14 +00:00
2019-11-22 23:11:37 +00:00
BEGIN_PS_NS
2019-08-26 03:59:14 +00:00
2020-08-20 16:35:52 +00:00
class LMFPlayer
{
enum
{
kFramePalette = 0 ,
kFrameSound ,
kFrameImage ,
kFrameDone
} ;
2019-08-26 03:59:14 +00:00
2020-08-20 16:35:52 +00:00
enum
{
kSampleRate = 22050 ,
kSampleSize = 2205 ,
numAudioBlocks = 20
} ;
2019-08-26 03:59:14 +00:00
2020-08-20 16:35:52 +00:00
struct AudioData
{
int16_t samples [ kSampleSize * numAudioBlocks ] ; // must be a multiple of the stream buffer size
int nWrite ;
int nRead ;
} ;
2019-08-26 03:59:14 +00:00
2020-08-20 16:35:52 +00:00
uint8_t palette [ 768 ] ;
uint8_t CurFrame [ 64000 ] ;
2019-08-26 03:59:14 +00:00
2020-08-20 16:35:52 +00:00
SoundStream * stream = nullptr ;
AudioData audio { } ;
2020-08-20 19:05:14 +00:00
AnimTextures animtex ;
2020-08-20 15:52:56 +00:00
2021-04-30 14:21:37 +00:00
FileReader fp ;
2020-08-20 16:35:52 +00:00
int nFrame = 0 ;
2020-08-20 15:52:56 +00:00
2021-04-30 14:21:37 +00:00
uint64_t nextclock = 0 ;
2020-08-20 16:35:52 +00:00
public :
2021-04-30 14:21:37 +00:00
LMFPlayer ( const char * filename )
{
fp = fileSystem . OpenFileReader ( filename ) ;
Open ( fp ) ;
}
bool Frame ( uint64_t clock )
{
if ( clock > = nextclock )
{
nextclock + = 100'000'000 ;
if ( ReadFrame ( fp ) = = 0 )
{
return true ;
}
}
return false ;
}
2022-09-11 06:09:26 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2020-08-20 16:35:52 +00:00
int ReadFrame ( FileReader & fp )
{
nFrame + + ;
2020-08-20 15:52:56 +00:00
2020-08-20 16:35:52 +00:00
uint8_t nType ;
uint8_t var_1C ;
int nSize ;
uint16_t yOffset ;
uint8_t xOffset ;
uint8_t nPixels ;
2019-10-12 21:09:55 +00:00
2020-08-20 16:35:52 +00:00
while ( 1 )
{
if ( fp . Read ( & nType , sizeof ( nType ) ) = = 0 )
{
return 0 ;
}
2019-08-31 07:47:15 +00:00
2020-08-20 16:35:52 +00:00
fp . Read ( & nSize , sizeof ( nSize ) ) ;
2019-08-31 07:47:15 +00:00
2020-08-20 16:35:52 +00:00
nType - - ;
if ( nType > 3 )
{
continue ;
}
2019-08-31 07:47:15 +00:00
2020-08-20 16:35:52 +00:00
switch ( nType )
{
2019-08-31 07:47:15 +00:00
case kFramePalette :
{
2020-04-12 05:46:53 +00:00
fp . Read ( palette , 768 ) ;
2019-11-24 12:59:36 +00:00
fp . Read ( & var_1C , sizeof ( var_1C ) ) ;
2019-08-31 07:47:15 +00:00
2020-08-20 16:35:52 +00:00
for ( unsigned i = 0 ; i < 768 ; i + + )
2020-04-12 05:46:53 +00:00
palette [ i ] < < = 2 ;
2019-08-31 07:47:15 +00:00
2020-08-20 16:35:52 +00:00
memset ( CurFrame , 0 , 4 ) ; //sizeof(CurFrame));
2019-08-31 07:47:15 +00:00
continue ;
}
case kFrameSound :
{
2020-08-20 15:52:56 +00:00
auto buffer = fp . Read ( nSize ) ;
2023-08-19 16:08:27 +00:00
assert ( buffer . size ( ) = = kSampleSize ) ;
2020-08-20 16:35:52 +00:00
auto wbuffer = audio . samples + audio . nWrite * kSampleSize ;
2020-08-20 15:52:56 +00:00
for ( int i = 0 ; i < 2205 ; i + + )
2019-12-05 21:18:34 +00:00
{
2020-08-20 15:52:56 +00:00
wbuffer [ i ] = ( buffer [ i ] - 128 ) < < 8 ;
2019-12-05 21:18:34 +00:00
}
2020-08-20 15:52:56 +00:00
audio . nWrite + + ;
if ( audio . nWrite > = numAudioBlocks ) audio . nWrite = 0 ;
2019-08-31 07:47:15 +00:00
continue ;
}
case kFrameImage :
{
2020-08-20 16:35:52 +00:00
if ( nSize = = 0 )
{
2019-08-31 07:47:15 +00:00
continue ;
}
2020-08-20 16:35:52 +00:00
uint8_t * pFrame = CurFrame ;
2019-08-31 07:47:15 +00:00
2021-05-12 15:09:07 +00:00
int nRead = ( int ) fp . Read ( & yOffset , sizeof ( yOffset ) ) ;
2019-08-31 07:47:15 +00:00
nSize - = nRead ;
pFrame + = yOffset * 200 ; // row position
while ( nSize > 0 )
{
2019-11-24 12:59:36 +00:00
fp . Read ( & xOffset , sizeof ( xOffset ) ) ;
fp . Read ( & nPixels , sizeof ( nPixels ) ) ;
2019-08-31 07:47:15 +00:00
nSize - = 2 ;
pFrame + = xOffset ;
if ( nPixels )
{
2021-12-24 09:48:35 +00:00
nRead = ( int ) fp . Read ( pFrame , nPixels ) ;
2019-08-31 07:47:15 +00:00
pFrame + = nRead ;
nSize - = nRead ;
}
}
2020-08-20 19:05:14 +00:00
animtex . SetFrame ( palette , CurFrame ) ;
2019-08-31 07:47:15 +00:00
break ;
}
case kFrameDone :
{
return 1 ;
break ;
}
2020-08-20 16:35:52 +00:00
}
2019-08-31 07:47:15 +00:00
}
}
2019-12-05 21:18:34 +00:00
2022-09-11 06:09:26 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2019-12-05 21:18:34 +00:00
2020-08-20 16:35:52 +00:00
static bool StreamCallbackFunc ( SoundStream * stream , void * buff , int len , void * userdata )
{
LMFPlayer * pId = ( LMFPlayer * ) userdata ;
memcpy ( buff , & pId - > audio . samples [ pId - > audio . nRead ] , len ) ;
pId - > audio . nRead + = len / 2 ;
2021-10-08 17:22:21 +00:00
if ( pId - > audio . nRead > = ( int ) countof ( pId - > audio . samples ) ) pId - > audio . nRead = 0 ;
2020-08-20 16:35:52 +00:00
return true ;
2019-12-05 21:18:34 +00:00
}
2020-08-20 16:35:52 +00:00
void Open ( FileReader & fp )
{
2020-08-20 15:52:56 +00:00
2020-08-20 16:35:52 +00:00
uint8_t header [ 32 ] ;
fp . Read ( header , sizeof ( header ) ) ;
audio . nWrite = 5 ; // play 5 blocks (i.e. half a second) of silence to get ahead of the stream. For this video it isn't necessary to sync it perfectly.
2020-08-20 15:52:56 +00:00
2020-08-20 16:35:52 +00:00
// start audio playback
2022-10-14 18:11:44 +00:00
stream = S_CreateCustomStream ( kSampleSize * 2 , kSampleRate , 1 , MusicSamples16bit , StreamCallbackFunc , this ) ; // size must be doubled here or dropouts can be heard.
2020-08-20 19:05:14 +00:00
animtex . SetSize ( AnimTexture : : Paletted , 200 , 320 ) ;
2020-08-20 16:35:52 +00:00
}
2020-02-29 11:55:12 +00:00
2020-08-20 19:05:14 +00:00
void Close ( )
2020-08-20 16:35:52 +00:00
{
2020-08-20 19:05:14 +00:00
S_StopCustomStream ( stream ) ;
2020-08-20 16:35:52 +00:00
}
2019-08-31 07:47:15 +00:00
2021-04-30 14:21:37 +00:00
FTextureID GetTexture ( )
2020-08-20 16:35:52 +00:00
{
2021-04-30 14:21:37 +00:00
return animtex . GetFrameID ( ) ;
2020-08-20 16:35:52 +00:00
}
} ;
2019-08-31 07:47:15 +00:00
2022-09-11 06:09:26 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-04-30 14:21:37 +00:00
int IdentifyLMF ( const FString * fn )
2020-08-20 19:05:14 +00:00
{
2021-04-30 14:21:37 +00:00
auto fp = fileSystem . OpenFileReader ( * fn ) ;
if ( ! fp . isOpen ( ) ) return false ;
char buffer [ 4 ] ;
fp . Read ( buffer , 4 ) ;
return ( 0 = = memcmp ( buffer , " LMF " , 4 ) ) ;
}
2019-08-31 07:47:15 +00:00
2021-04-30 14:21:37 +00:00
DEFINE_ACTION_FUNCTION ( _LMFDecoder , Create )
{
PARAM_PROLOGUE ;
PARAM_STRING ( fn ) ;
ACTION_RETURN_POINTER ( new LMFPlayer ( fn ) ) ;
}
2020-08-20 15:52:56 +00:00
2021-04-30 14:21:37 +00:00
DEFINE_ACTION_FUNCTION_NATIVE ( _LMFDecoder , Identify , IdentifyLMF )
{
PARAM_PROLOGUE ;
PARAM_STRING ( fn ) ;
ACTION_RETURN_BOOL ( IdentifyLMF ( & fn ) ) ;
}
2020-08-20 15:52:56 +00:00
2021-04-30 14:21:37 +00:00
DEFINE_ACTION_FUNCTION ( _LMFDecoder , Frame )
{
PARAM_SELF_STRUCT_PROLOGUE ( LMFPlayer ) ;
PARAM_FLOAT ( clock ) ;
ACTION_RETURN_BOOL ( self - > Frame ( uint64_t ( clock ) ) ) ;
}
2019-08-31 07:47:15 +00:00
2021-04-30 14:21:37 +00:00
DEFINE_ACTION_FUNCTION ( _LMFDecoder , GetTexture )
2020-08-20 19:05:14 +00:00
{
2021-04-30 14:21:37 +00:00
PARAM_SELF_STRUCT_PROLOGUE ( LMFPlayer ) ;
ACTION_RETURN_INT ( self - > GetTexture ( ) . GetIndex ( ) ) ;
}
2020-08-20 19:05:14 +00:00
2021-04-30 14:21:37 +00:00
DEFINE_ACTION_FUNCTION ( _LMFDecoder , Close )
{
PARAM_SELF_STRUCT_PROLOGUE ( LMFPlayer ) ;
self - > Close ( ) ;
delete self ;
return 0 ;
2019-08-26 03:59:14 +00:00
}
2021-04-30 14:21:37 +00:00
2020-07-23 14:35:54 +00:00
2019-11-22 23:11:37 +00:00
END_PS_NS