2023-08-21 19:56:10 +00:00
/**
* Furnace Tracker - multi - system chiptune tracker
2024-01-17 02:26:57 +00:00
* Copyright ( C ) 2021 - 2024 tildearrow and contributors
2023-08-21 19:56:10 +00:00
*
* This program 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 ; either version 2 of the License , or
* ( at your option ) any later version .
*
* 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 .
*/
# include "engine.h"
# include "../ta-log.h"
# include "../fileutils.h"
# ifdef HAVE_SNDFILE
# include "sfWrapper.h"
# endif
DivSample * DivEngine : : sampleFromFile ( const char * path ) {
if ( song . sample . size ( ) > = 256 ) {
lastError = " too many samples! " ;
return NULL ;
}
BUSY_BEGIN ;
warnings = " " ;
const char * pathRedux = strrchr ( path , DIR_SEPARATOR ) ;
if ( pathRedux = = NULL ) {
pathRedux = path ;
} else {
pathRedux + + ;
}
String stripPath ;
const char * pathReduxEnd = strrchr ( pathRedux , ' . ' ) ;
if ( pathReduxEnd = = NULL ) {
stripPath = pathRedux ;
} else {
for ( const char * i = pathRedux ; i ! = pathReduxEnd & & ( * i ) ; i + + ) {
stripPath + = * i ;
}
}
const char * ext = strrchr ( path , ' . ' ) ;
if ( ext ! = NULL ) {
String extS ;
for ( ; * ext ; ext + + ) {
char i = * ext ;
if ( i > = ' A ' & & i < = ' Z ' ) {
i + = ' a ' - ' A ' ;
}
extS + = i ;
}
if ( extS = = " .dmc " | | extS = = " .brr " ) { // read as .dmc or .brr
size_t len = 0 ;
DivSample * sample = new DivSample ;
sample - > name = stripPath ;
FILE * f = ps_fopen ( path , " rb " ) ;
if ( f = = NULL ) {
BUSY_END ;
lastError = fmt : : sprintf ( " could not open file! (%s) " , strerror ( errno ) ) ;
delete sample ;
return NULL ;
}
if ( fseek ( f , 0 , SEEK_END ) < 0 ) {
fclose ( f ) ;
BUSY_END ;
lastError = fmt : : sprintf ( " could not get file length! (%s) " , strerror ( errno ) ) ;
delete sample ;
return NULL ;
}
len = ftell ( f ) ;
if ( len = = 0 ) {
fclose ( f ) ;
BUSY_END ;
lastError = " file is empty! " ;
delete sample ;
return NULL ;
}
if ( len = = ( SIZE_MAX > > 1 ) ) {
fclose ( f ) ;
BUSY_END ;
lastError = " file is invalid! " ;
delete sample ;
return NULL ;
}
if ( fseek ( f , 0 , SEEK_SET ) < 0 ) {
fclose ( f ) ;
BUSY_END ;
lastError = fmt : : sprintf ( " could not seek to beginning of file! (%s) " , strerror ( errno ) ) ;
delete sample ;
return NULL ;
}
if ( extS = = " .dmc " ) {
sample - > rate = 33144 ;
sample - > centerRate = 33144 ;
sample - > depth = DIV_SAMPLE_DEPTH_1BIT_DPCM ;
sample - > init ( len * 8 ) ;
} else if ( extS = = " .brr " ) {
sample - > rate = 32000 ;
sample - > centerRate = 32000 ;
sample - > depth = DIV_SAMPLE_DEPTH_BRR ;
sample - > init ( 16 * ( len / 9 ) ) ;
} else {
fclose ( f ) ;
BUSY_END ;
lastError = " wait... is that right? no I don't think so... " ;
delete sample ;
return NULL ;
}
unsigned char * dataBuf = sample - > dataDPCM ;
if ( extS = = " .brr " ) {
dataBuf = sample - > dataBRR ;
if ( ( len % 9 ) = = 2 ) {
// read loop position
unsigned short loopPos = 0 ;
logD ( " BRR file has loop position " ) ;
if ( fread ( & loopPos , 1 , 2 , f ) ! = 2 ) {
logW ( " could not read loop position! %s " , strerror ( errno ) ) ;
} else {
# ifdef TA_BIG_ENDIAN
loopPos = ( loopPos > > 8 ) | ( loopPos < < 8 ) ;
# endif
sample - > loopStart = 16 * ( loopPos / 9 ) ;
sample - > loopEnd = sample - > samples ;
sample - > loop = true ;
sample - > loopMode = DIV_SAMPLE_LOOP_FORWARD ;
}
len - = 2 ;
if ( len = = 0 ) {
fclose ( f ) ;
BUSY_END ;
lastError = " BRR sample is empty! " ;
delete sample ;
return NULL ;
}
} else if ( ( len % 9 ) ! = 0 ) {
fclose ( f ) ;
BUSY_END ;
lastError = " possibly corrupt BRR sample! " ;
delete sample ;
return NULL ;
}
}
if ( fread ( dataBuf , 1 , len , f ) = = 0 ) {
fclose ( f ) ;
BUSY_END ;
lastError = fmt : : sprintf ( " could not read file! (%s) " , strerror ( errno ) ) ;
delete sample ;
return NULL ;
}
BUSY_END ;
return sample ;
}
}
# ifndef HAVE_SNDFILE
lastError = " Furnace was not compiled with libsndfile! " ;
return NULL ;
# else
SF_INFO si ;
SFWrapper sfWrap ;
memset ( & si , 0 , sizeof ( SF_INFO ) ) ;
SNDFILE * f = sfWrap . doOpen ( path , SFM_READ , & si ) ;
if ( f = = NULL ) {
BUSY_END ;
int err = sf_error ( NULL ) ;
if ( err = = SF_ERR_SYSTEM ) {
lastError = fmt : : sprintf ( " could not open file! (%s %s) " , sf_error_number ( err ) , strerror ( errno ) ) ;
} else {
lastError = fmt : : sprintf ( " could not open file! (%s) \n if this is raw sample data, you may import it by right-clicking the Load Sample icon and selecting \" import raw \" . " , sf_error_number ( err ) ) ;
}
return NULL ;
}
if ( si . frames > 16777215 ) {
lastError = " this sample is too big! max sample size is 16777215. " ;
sfWrap . doClose ( ) ;
BUSY_END ;
return NULL ;
}
void * buf = NULL ;
sf_count_t sampleLen = sizeof ( short ) ;
if ( ( si . format & SF_FORMAT_SUBMASK ) = = SF_FORMAT_PCM_U8 ) {
logD ( " sample is 8-bit unsigned " ) ;
buf = new unsigned char [ si . channels * si . frames ] ;
sampleLen = sizeof ( unsigned char ) ;
} else if ( ( si . format & SF_FORMAT_SUBMASK ) = = SF_FORMAT_FLOAT ) {
logD ( " sample is 32-bit float " ) ;
buf = new float [ si . channels * si . frames ] ;
sampleLen = sizeof ( float ) ;
} else {
logD ( " sample is 16-bit signed " ) ;
buf = new short [ si . channels * si . frames ] ;
sampleLen = sizeof ( short ) ;
}
if ( ( si . format & SF_FORMAT_SUBMASK ) = = SF_FORMAT_PCM_U8 | | ( si . format & SF_FORMAT_SUBMASK ) = = SF_FORMAT_FLOAT ) {
if ( sf_read_raw ( f , buf , si . frames * si . channels * sampleLen ) ! = ( si . frames * si . channels * sampleLen ) ) {
logW ( " sample read size mismatch! " ) ;
}
} else {
if ( sf_read_short ( f , ( short * ) buf , si . frames * si . channels ) ! = ( si . frames * si . channels ) ) {
logW ( " sample read size mismatch! " ) ;
}
}
DivSample * sample = new DivSample ;
int sampleCount = ( int ) song . sample . size ( ) ;
sample - > name = stripPath ;
int index = 0 ;
if ( ( si . format & SF_FORMAT_SUBMASK ) = = SF_FORMAT_PCM_U8 ) {
sample - > depth = DIV_SAMPLE_DEPTH_8BIT ;
} else {
sample - > depth = DIV_SAMPLE_DEPTH_16BIT ;
}
sample - > init ( si . frames ) ;
if ( ( si . format & SF_FORMAT_SUBMASK ) = = SF_FORMAT_PCM_U8 ) {
for ( int i = 0 ; i < si . frames * si . channels ; i + = si . channels ) {
int averaged = 0 ;
for ( int j = 0 ; j < si . channels ; j + + ) {
averaged + = ( ( int ) ( ( unsigned char * ) buf ) [ i + j ] ) - 128 ;
}
averaged / = si . channels ;
sample - > data8 [ index + + ] = averaged ;
}
delete [ ] ( unsigned char * ) buf ;
} else if ( ( si . format & SF_FORMAT_SUBMASK ) = = SF_FORMAT_FLOAT ) {
for ( int i = 0 ; i < si . frames * si . channels ; i + = si . channels ) {
float averaged = 0.0f ;
for ( int j = 0 ; j < si . channels ; j + + ) {
averaged + = ( ( float * ) buf ) [ i + j ] ;
}
averaged / = si . channels ;
averaged * = 32767.0 ;
if ( averaged < - 32768.0 ) averaged = - 32768.0 ;
if ( averaged > 32767.0 ) averaged = 32767.0 ;
sample - > data16 [ index + + ] = averaged ;
}
delete [ ] ( float * ) buf ;
} else {
for ( int i = 0 ; i < si . frames * si . channels ; i + = si . channels ) {
int averaged = 0 ;
for ( int j = 0 ; j < si . channels ; j + + ) {
averaged + = ( ( short * ) buf ) [ i + j ] ;
}
averaged / = si . channels ;
sample - > data16 [ index + + ] = averaged ;
}
delete [ ] ( short * ) buf ;
}
sample - > rate = si . samplerate ;
if ( sample - > rate < 4000 ) sample - > rate = 4000 ;
if ( sample - > rate > 96000 ) sample - > rate = 96000 ;
sample - > centerRate = si . samplerate ;
SF_INSTRUMENT inst ;
if ( sf_command ( f , SFC_GET_INSTRUMENT , & inst , sizeof ( inst ) ) = = SF_TRUE )
{
// There's no documentation on libsndfile detune range, but the code
// implies -50..50. Yet when loading a file you can get a >50 value.
2023-09-08 00:00:31 +00:00
// disabled for now
/*
2023-08-21 19:56:10 +00:00
if ( inst . detune > 50 )
inst . detune = inst . detune - 100 ;
short pitch = ( ( 0x3c - inst . basenote ) * 100 ) + inst . detune ;
sample - > centerRate = si . samplerate * pow ( 2.0 , pitch / ( 12.0 * 100.0 ) ) ;
2023-09-08 00:00:31 +00:00
*/
2023-08-21 19:56:10 +00:00
if ( inst . loop_count & & inst . loops [ 0 ] . mode > = SF_LOOP_FORWARD )
{
sample - > loop = true ;
sample - > loopMode = ( DivSampleLoopMode ) ( inst . loops [ 0 ] . mode - SF_LOOP_FORWARD ) ;
sample - > loopStart = inst . loops [ 0 ] . start ;
sample - > loopEnd = inst . loops [ 0 ] . end ;
if ( inst . loops [ 0 ] . end < ( unsigned int ) sampleCount )
sampleCount = inst . loops [ 0 ] . end ;
}
else
sample - > loop = false ;
}
if ( sample - > centerRate < 4000 ) sample - > centerRate = 4000 ;
if ( sample - > centerRate > 64000 ) sample - > centerRate = 64000 ;
sfWrap . doClose ( ) ;
BUSY_END ;
return sample ;
# endif
}
2024-01-17 02:12:34 +00:00
DivSample * DivEngine : : sampleFromFileRaw ( const char * path , DivSampleDepth depth , int channels , bool bigEndian , bool unsign , bool swapNibbles , int rate ) {
2023-08-21 19:56:10 +00:00
if ( song . sample . size ( ) > = 256 ) {
lastError = " too many samples! " ;
return NULL ;
}
if ( channels < 1 ) {
channels = 1 ;
}
if ( depth ! = DIV_SAMPLE_DEPTH_8BIT & & depth ! = DIV_SAMPLE_DEPTH_16BIT ) {
if ( channels ! = 1 ) {
channels = 1 ;
}
}
BUSY_BEGIN ;
warnings = " " ;
const char * pathRedux = strrchr ( path , DIR_SEPARATOR ) ;
if ( pathRedux = = NULL ) {
pathRedux = path ;
} else {
pathRedux + + ;
}
String stripPath ;
const char * pathReduxEnd = strrchr ( pathRedux , ' . ' ) ;
if ( pathReduxEnd = = NULL ) {
stripPath = pathRedux ;
} else {
for ( const char * i = pathRedux ; i ! = pathReduxEnd & & ( * i ) ; i + + ) {
stripPath + = * i ;
}
}
size_t len = 0 ;
size_t lenDivided = 0 ;
DivSample * sample = new DivSample ;
sample - > name = stripPath ;
FILE * f = ps_fopen ( path , " rb " ) ;
if ( f = = NULL ) {
BUSY_END ;
lastError = fmt : : sprintf ( " could not open file! (%s) " , strerror ( errno ) ) ;
delete sample ;
return NULL ;
}
if ( fseek ( f , 0 , SEEK_END ) < 0 ) {
fclose ( f ) ;
BUSY_END ;
lastError = fmt : : sprintf ( " could not get file length! (%s) " , strerror ( errno ) ) ;
delete sample ;
return NULL ;
}
len = ftell ( f ) ;
if ( len = = 0 ) {
fclose ( f ) ;
BUSY_END ;
lastError = " file is empty! " ;
delete sample ;
return NULL ;
}
if ( len = = ( SIZE_MAX > > 1 ) ) {
fclose ( f ) ;
BUSY_END ;
lastError = " file is invalid! " ;
delete sample ;
return NULL ;
}
if ( fseek ( f , 0 , SEEK_SET ) < 0 ) {
fclose ( f ) ;
BUSY_END ;
lastError = fmt : : sprintf ( " could not seek to beginning of file! (%s) " , strerror ( errno ) ) ;
delete sample ;
return NULL ;
}
lenDivided = len / channels ;
unsigned int samples = lenDivided ;
switch ( depth ) {
case DIV_SAMPLE_DEPTH_1BIT :
case DIV_SAMPLE_DEPTH_1BIT_DPCM :
samples = lenDivided * 8 ;
break ;
case DIV_SAMPLE_DEPTH_YMZ_ADPCM :
case DIV_SAMPLE_DEPTH_QSOUND_ADPCM :
case DIV_SAMPLE_DEPTH_ADPCM_A :
case DIV_SAMPLE_DEPTH_ADPCM_B :
2023-08-29 09:26:25 +00:00
case DIV_SAMPLE_DEPTH_ADPCM_K :
2023-08-21 19:56:10 +00:00
case DIV_SAMPLE_DEPTH_VOX :
samples = lenDivided * 2 ;
break ;
2024-04-05 21:11:23 +00:00
case DIV_SAMPLE_DEPTH_IMA_ADPCM :
samples = ( lenDivided - 4 ) * 2 ;
break ;
2023-08-21 19:56:10 +00:00
case DIV_SAMPLE_DEPTH_8BIT :
case DIV_SAMPLE_DEPTH_MULAW :
2023-08-29 01:31:22 +00:00
case DIV_SAMPLE_DEPTH_C219 :
2023-08-21 19:56:10 +00:00
samples = lenDivided ;
break ;
case DIV_SAMPLE_DEPTH_BRR :
samples = 16 * ( ( lenDivided + 8 ) / 9 ) ;
break ;
case DIV_SAMPLE_DEPTH_16BIT :
samples = ( lenDivided + 1 ) / 2 ;
break ;
default :
break ;
}
if ( samples > 16777215 ) {
fclose ( f ) ;
BUSY_END ;
lastError = " this sample is too big! max sample size is 16777215. " ;
delete sample ;
return NULL ;
}
2024-01-17 02:12:34 +00:00
sample - > rate = rate ;
sample - > centerRate = rate ;
2023-08-21 19:56:10 +00:00
sample - > depth = depth ;
sample - > init ( samples ) ;
unsigned char * buf = new unsigned char [ len ] ;
if ( fread ( buf , 1 , len , f ) = = 0 ) {
fclose ( f ) ;
BUSY_END ;
lastError = fmt : : sprintf ( " could not read file! (%s) " , strerror ( errno ) ) ;
delete [ ] buf ;
delete sample ;
return NULL ;
}
fclose ( f ) ;
// import sample
size_t pos = 0 ;
if ( depth = = DIV_SAMPLE_DEPTH_16BIT ) {
for ( unsigned int i = 0 ; i < samples ; i + + ) {
int accum = 0 ;
for ( int j = 0 ; j < channels ; j + + ) {
if ( pos + 1 > = len ) break ;
if ( bigEndian ) {
accum + = ( short ) ( ( ( short ) ( ( buf [ pos ] < < 8 ) | buf [ pos + 1 ] ) ) ^ ( unsign ? 0x8000 : 0 ) ) ;
} else {
accum + = ( short ) ( ( ( short ) ( buf [ pos ] | ( buf [ pos + 1 ] < < 8 ) ) ) ^ ( unsign ? 0x8000 : 0 ) ) ;
}
pos + = 2 ;
}
accum / = channels ;
sample - > data16 [ i ] = accum ;
}
} else if ( depth = = DIV_SAMPLE_DEPTH_8BIT ) {
for ( unsigned int i = 0 ; i < samples ; i + + ) {
int accum = 0 ;
for ( int j = 0 ; j < channels ; j + + ) {
if ( pos > = len ) break ;
accum + = ( signed char ) ( buf [ pos + + ] ^ ( unsign ? 0x80 : 0 ) ) ;
}
accum / = channels ;
sample - > data8 [ i ] = accum ;
}
2023-09-25 10:09:56 +00:00
if ( bigEndian ) {
for ( unsigned int i = 0 ; ( i + 1 ) < samples ; i + = 2 ) {
sample - > data8 [ i ] ^ = sample - > data8 [ i ^ 1 ] ;
sample - > data8 [ i ^ 1 ] ^ = sample - > data8 [ i ] ;
sample - > data8 [ i ] ^ = sample - > data8 [ i ^ 1 ] ;
}
}
2023-08-21 19:56:10 +00:00
} else {
memcpy ( sample - > getCurBuf ( ) , buf , len ) ;
}
delete [ ] buf ;
if ( swapNibbles ) {
unsigned char * b = ( unsigned char * ) sample - > getCurBuf ( ) ;
switch ( depth ) {
case DIV_SAMPLE_DEPTH_1BIT :
case DIV_SAMPLE_DEPTH_1BIT_DPCM :
// reverse bit order
for ( unsigned int i = 0 ; i < sample - > getCurBufLen ( ) ; i + + ) {
b [ i ] = (
( ( b [ i ] & 128 ) ? 1 : 0 ) |
( ( b [ i ] & 64 ) ? 2 : 0 ) |
( ( b [ i ] & 32 ) ? 4 : 0 ) |
( ( b [ i ] & 16 ) ? 8 : 0 ) |
( ( b [ i ] & 8 ) ? 16 : 0 ) |
( ( b [ i ] & 4 ) ? 32 : 0 ) |
( ( b [ i ] & 2 ) ? 64 : 0 ) |
( ( b [ i ] & 1 ) ? 128 : 0 )
) ;
}
break ;
case DIV_SAMPLE_DEPTH_YMZ_ADPCM :
case DIV_SAMPLE_DEPTH_QSOUND_ADPCM :
case DIV_SAMPLE_DEPTH_ADPCM_A :
case DIV_SAMPLE_DEPTH_ADPCM_B :
2023-08-29 09:26:25 +00:00
case DIV_SAMPLE_DEPTH_ADPCM_K :
2023-08-21 19:56:10 +00:00
case DIV_SAMPLE_DEPTH_VOX :
// swap nibbles
for ( unsigned int i = 0 ; i < sample - > getCurBufLen ( ) ; i + + ) {
b [ i ] = ( b [ i ] < < 4 ) | ( b [ i ] > > 4 ) ;
}
break ;
case DIV_SAMPLE_DEPTH_MULAW :
// Namco to G.711
// Namco: smmmmxxx
// G.711: sxxxmmmm (^0xff)
for ( unsigned int i = 0 ; i < sample - > getCurBufLen ( ) ; i + + ) {
b [ i ] = ( ( ( b [ i ] & 7 ) < < 4 ) | ( ( ( b [ i ] > > 3 ) & 15 ) ^ ( ( b [ i ] & 0x80 ) ? 15 : 0 ) ) | ( b [ i ] & 0x80 ) ) ^ 0xff ;
}
break ;
default :
break ;
}
}
BUSY_END ;
return sample ;
}