winamp/Src/Plugins/DSP/dsp_sc/sc2srclib/Include/shoutcast_output.h

817 lines
22 KiB
C++

#ifndef __SHOUTCAST_OUTPUT_H__
#define __SHOUTCAST_OUTPUT_H__
#include <time.h>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <list>
#include <fstream>
#include "c_serial_jobmanager.h"
#include "../Components/wac_network/wac_network_connection_api.h"
#include <WinSock2.h>
#include "../Encoders/c_encoder.h"
#include "../Encoders/c_encoder_mp3dll.h"
#include "../lame/include/lame.h"
#include "../lame/libmp3lame/lame_global_flags.h"
#include "../uvAuth21/uvAuth21.h"
#define UV_SYNC_BYTE 0x5A
#define UV_RESERVED 0x00
#define UV_END 0x00
#define UV_END_LEN 1
#define UV_HEADER_LEN 6
#define UV_META_LEN 6
#define UV_FRAME_LEN 16384
#define UV_MAX_DATA_LEN (UV_FRAME_LEN - UV_HEADER_LEN - UV_END_LEN)
#define UV_MAX_META_LEN (UV_FRAME_LEN - UV_HEADER_LEN - UV_META_LEN - UV_END_LEN)
#define UV_MAX_META_FRAGMENTS 32
#define UV_MAX_TOTAL_META_LEN (UV_MAX_META_LEN * UV_MAX_META_FRAGMENTS)
typedef struct {
char* name;
bool parent;
bool children;
} SCgenres;
static SCgenres genres[] = {{"Alternative", true, true},
{"Adult Alternative", false},
{"Britpop", false},
{"Classic Alternative", false},
{"College", false},
{"Dancepunk", false},
{"Dream Pop", false},
{"Emo", false},
{"Goth", false},
{"Grunge", false},
{"Hardcore", false},
{"Indie Pop", false},
{"Indie Rock", false},
{"Industrial", false},
{"LoFi", false},
{"Modern Rock", false},
{"New Wave", false},
{"Noise Pop", false},
{"Post Punk", false},
{"Power Pop", false},
{"Punk", false},
{"Ska", false},
{"Xtreme", false},
{"Blues", true, true},
{"Acoustic Blues", false},
{"Cajun and Zydeco", false},
{"Chicago Blues", false},
{"Contemporary Blues", false},
{"Country Blues", false},
{"Delta Blues", false},
{"Electric Blues", false},
{"Classical", true, true},
{"Baroque", false},
{"Chamber", false},
{"Choral", false},
{"Classical Period", false},
{"Early Classical", false},
{"Impressionist", false},
{"Modern", false},
{"Opera", false},
{"Piano", false},
{"Romantic", false},
{"Symphony", false},
{"Country", true, true},
{"Alt Country", false},
{"Americana", false},
{"Bluegrass", false},
{"Classic Country", false},
{"Contemporary Bluegrass", false},
{"Contemporary Country", false},
{"Honky Tonk", false},
{"Hot Country Hits", false},
{"Western", false},
{"Decades", true, true},
{"30s", false},
{"40s", false},
{"50s", false},
{"60s", false},
{"70s", false},
{"80s", false},
{"90s", false},
{"00s", false},
{"Easy Listening", true, true},
{"Exotica", false},
{"Light Rock", false},
{"Lounge", false},
{"Orchestral Pop", false},
{"Polka", false},
{"Space Age Pop", false},
{"Electronic", true, true},
{"Acid House", false},
{"Ambient", false},
{"Big Beat", false},
{"Breakbeat", false},
{"Dance", false},
{"Demo", false},
{"Disco", false},
{"Downtempo", false},
{"Drum and Bass", false},
{"Dubstep", false},
{"Electro", false},
{"Garage", false},
{"Hard House", false},
{"House", false},
{"IDM", false},
{"Jungle", false},
{"Progressive", false},
{"Techno", false},
{"Trance", false},
{"Tribal", false},
{"Trip Hop", false},
{"Folk", true, true},
{"Alternative Folk", false},
{"Contemporary Folk", false},
{"Folk Rock", false},
{"New Acoustic", false},
{"Old Time", false},
{"Traditional Folk", false},
{"World Folk", false},
{"Inspirational", true, true},
{"Christian", false},
{"Christian Metal", false},
{"Christian Rap", false},
{"Christian Rock", false},
{"Classic Christian", false},
{"Contemporary Gospel", false},
{"Gospel", false},
{"Praise and Worship", false},
{"Sermons and Services", false},
{"Southern Gospel", false},
{"Traditional Gospel", false},
{"International", true, true},
{"African", false},
{"Afrikaans", false},
{"Arabic", false},
{"Asian", false},
{"Bollywood", false},
{"Brazilian", false},
{"Caribbean", false},
{"Celtic", false},
{"Creole", false},
{"European", false},
{"Filipino", false},
{"French", false},
{"German", false},
{"Greek", false},
{"Hawaiian and Pacific", false},
{"Hebrew", false},
{"Hindi", false},
{"Indian", false},
{"Islamic", false},
{"Japanese", false},
{"Klezmer", false},
{"Korean", false},
{"Mediterranean", false},
{"Middle Eastern", false},
{"North American", false},
{"Russian", false},
{"Soca", false},
{"South American", false},
{"Tamil", false},
{"Turkish", false},
{"Worldbeat", false},
{"Zouk", false},
{"Jazz", true, true},
{"Acid Jazz", false},
{"Avant Garde", false},
{"Big Band", false},
{"Bop", false},
{"Classic Jazz", false},
{"Cool Jazz", false},
{"Fusion", false},
{"Hard Bop", false},
{"Latin Jazz", false},
{"Smooth Jazz", false},
{"Swing", false},
{"Vocal Jazz", false},
{"World Fusion", false},
{"Latin", true, true},
{"Bachata", false},
{"Banda", false},
{"Bossa Nova", false},
{"Cumbia", false},
{"Flamenco", false},
{"Latin Dance", false},
{"Latin Pop", false},
{"Latin Rap and Hip Hop", false},
{"Latin Rock", false},
{"Mariachi", false},
{"Merengue", false},
{"Ranchera", false},
{"Reggaeton", false},
{"Regional Mexican", false},
{"Salsa", false},
{"Samba", false},
{"Tango", false},
{"Tejano", false},
{"Tropicalia", false},
{"Metal", true, true},
{"Black Metal", false},
{"Classic Metal", false},
{"Death Metal", false},
{"Extreme Metal", false},
{"Grindcore", false},
{"Hair Metal", false},
{"Heavy Metal", false},
{"Metalcore", false},
{"Power Metal", false},
{"Progressive Metal", false},
{"Rap Metal", false},
{"Thrash Metal", false},
{"Misc", true, false},
{"New Age", true, true},
{"Environmental", false},
{"Ethnic Fusion", false},
{"Healing", false},
{"Meditation", false},
{"Spiritual", false},
{"Pop", true, true},
{"Adult Contemporary", false},
{"Barbershop", false},
{"Bubblegum Pop", false},
{"Dance Pop", false},
{"Idols", false},
{"JPOP", false},
{"KPOP", false},
{"Oldies", false},
{"Soft Rock", false},
{"Teen Pop", false},
{"Top 40", false},
{"World Pop", false},
{"Public Radio", true, true},
{"College", false},
{"News", false},
{"Sports", false},
{"Talk", false},
{"Weather", false},
{"R&B and Urban", true, false},
{"Classic R&B", false},
{"Contemporary R&B", false},
{"Doo Wop", false},
{"Funk", false},
{"Motown", false},
{"Neo Soul", false},
{"Quiet Storm", false},
{"Soul", false},
{"Urban Contemporary", false},
{"Rap", true, true},
{"Alternative Rap", false},
{"Dirty South", false},
{"East Coast Rap", false},
{"Freestyle", false},
{"Gangsta Rap", false},
{"Hip Hop", false},
{"Mixtapes", false},
{"Old School", false},
{"Turntablism", false},
{"Underground Hip Hop", false},
{"West Coast Rap", false},
{"Reggae", true, true},
{"Contemporary Reggae", false},
{"Dancehall", false},
{"Dub", false},
{"Pop Reggae", false},
{"Ragga", false},
{"Reggae Roots", false},
{"Rock Steady", false},
{"Rock", true, true},
{"Adult Album Alternative", false},
{"British Invasion", false},
{"Celtic Rock", false},
{"Classic Rock", false},
{"Garage Rock", false},
{"Glam", false},
{"Hard Rock", false},
{"Jam Bands", false},
{"JROCK", false},
{"Piano Rock", false},
{"Prog Rock", false},
{"Psychedelic", false},
{"Rock & Roll", false},
{"Rockabilly", false},
{"Singer and Songwriter", false},
{"Surf", false},
{"Seasonal and Holiday", true, true},
{"Anniversary", false},
{"Birthday", false},
{"Christmas", false},
{"Halloween", false},
{"Hanukkah", false},
{"Honeymoon", false},
{"Kwanzaa", false},
{"Valentine", false},
{"Wedding", false},
{"Winter", false},
{"Soundtracks", true, true},
{"Anime", false},
{"Kids", false},
{"Original Score", false},
{"Showtunes", false},
{"Video Game Music", false},
{"Talk", true, true},
{"BlogTalk", false},
{"Comedy", false},
{"Community", false},
{"Educational", false},
{"Government", false},
{"News", false},
{"Old Time Radio", false},
{"Other Talk", false},
{"Political", false},
{"Scanner", false},
{"Spoken Word", false},
{"Sports", false},
{"Technology", false},
{"Themes", true, true},
{"Adult", false},
{"Best Of", false},
{"Chill", false},
{"Eclectic", false},
{"Experimental", false},
{"Female", false},
{"Heartache", false},
{"Instrumental", false},
{"LGBT", false},
{"Love and Romance", false},
{"Party Mix", false},
{"Patriotic", false},
{"Rainy Day Mix", false},
{"Reality", false},
{"Sexy", false},
{"Shuffle", false},
{"Travel Mix", false},
{"Tribute", false},
{"Trippy", false},
{"Work Mix", false}
};
// pulled from nmrCommon\intTypes.h
typedef unsigned char __uint8;
typedef unsigned short __uint16;
typedef unsigned int __uint32;
typedef unsigned long long __uint64;
#pragma pack(push,1)
// this structure should be 16384 bytes in total size
// and is defined in full size so that we know its ok
struct uv2xHdr
{ // uvox2 message
__uint8 sync;
__uint8 qos;
__uint16 msgType;
__uint16 msgLen;
__uint8 m_data[UV_MAX_DATA_LEN];
__uint8 end;
};
struct uv2xMetadataHdr
{ /* uvox 2 metadata header */
__uint8 sync;
__uint8 qos;
__uint16 msgType;
__uint16 msgLen;
__uint16 id; /* ID (cookie) identifying a metadata package */
__uint16 span; /* Span of messages in the metadata package being assembled */
__uint16 index; /* Index of the message in the metadata package being assembled */
__uint8 m_data[UV_MAX_META_LEN];
__uint8 end;
};
#pragma pack(pop)
#define MSG_AUTH 0x1001
#define MSG_BROADCAST_SETUP 0x1002
#define MSG_NEGOTIATE_BUFFER_SIZE 0x1003
#define MSG_STANDBY 0x1004
#define MSG_TERMINATE 0x1005
#define MSG_FLUSH_CACHED_METADATA 0x1006
#define MSG_LISTENER_AUTHENTICATION 0x1007
#define MSG_MAX_PAYLOAD_SIZE 0x1008
#define MSG_CIPHER 0x1009
#define MSG_MIME_TYPE 0x1040
#define MSG_FILE_TRANSFER_BEGIN 0x1050
#define MSG_FILE_TRANSFER_DATA 0x1051
#define MSG_BROADCAST_INTERRUPTION 0x2001
#define MSG_BROADCAST_TERMINATE 0x2002
#define MSG_ICYNAME 0x1100
#define MSG_ICYGENRE 0x1101
#define MSG_ICYURL 0x1102
#define MSG_ICYPUB 0x1103
#define MSG_METADATA_CONTENTINFO 0x3000
#define MSG_METADATA_URL 0x3001
#define MSG_METADATA_XML 0x3901
#define MSG_METADATA_XML_NEW 0x3902
// only id the start of the album art type as it's variable
#define MSG_METADATA_ALBUMART 0x4000
#define MSG_METADATA_STATION_ART 0x0000
#define MSG_METADATA_PLAYING_ART 0x0100
/*
0x4 0x0xx Station logo
0x4 0x1xx Album art
00 = image/jpeg
01 = image/png
02 = image/bmp
03 = image/gif
*/
#define MSG_METADATA_TIMEREMAINING 0x5001
#define MP3_DATA 0x7000
#define VLB_DATA 0x8000
#define AAC_LC_DATA 0x8001
#define AACP_DATA 0x8003
#define OGG_DATA 0x8004
struct T_OUTPUT_CONFIG {
char Name[32];
wchar_t DisplayName[32];
char UserID[256];
char Address[1024];
u_short Port;
char StationID[12];
char Password[256]; // 4 - 8 for 1.8.2
char cipherkey[64]; // sc2 cipherkey
int AutoRecon;
int ReconTime;
char Description[1024];
char ServerURL[2048];
char Genre[256];
char ICQ[128];
char AIM[1024];
char IRC[1024];
int Public;
int doTitleUpdate;
int protocol;
int protocol_retry;
char Now[1024];
char Next[1024];
};
#define DEFAULT_ENCODER (C_ENCODER *)(-1)
#define OM_ENCODE 1
#define OM_OUTPUT 2
#define OM_OTHER 4
#define OM_ALL (OM_ENCODE | OM_OUTPUT | OM_OTHER)
enum OUTPUTTYPE {
OUTTYPE_SOURCE,
OUTTYPE_TITLE,
};
typedef unsigned long ARGB32;
struct T_OUTPUT_TITLE {
wchar_t *Title;
wchar_t *Song;
wchar_t *Album;
wchar_t *Artist;
wchar_t *Genre;
wchar_t *Comment;
wchar_t *Year;
std::vector<std::wstring> NextList;
void *APIC[2];
int APICLength[2];
int APICType[2];
T_OUTPUT_TITLE() : Title(0), Song(0), Album(0), Artist(0), Genre(0), Comment(0), Year(0)
{
memset(APIC, 0, sizeof(void *) * 2);
memset(APICLength, 0, sizeof(int) * 2);
memset(APICType, 0, sizeof(int) * 2);
}
~T_OUTPUT_TITLE()
{
if (Title)
{
free(Title);
Title = 0;
}
if (Song)
{
free(Song);
Song = 0;
}
if (Album)
{
free(Album);
Album = 0;
}
if (Artist)
{
free(Artist);
Artist = 0;
}
if (Genre)
{
free(Genre);
Genre = 0;
}
if (Comment)
{
free(Comment);
Comment = 0;
}
if (Year)
{
free(Year);
Year = 0;
}
}
};
struct T_OUTPUT_INFO {
unsigned int BytesSent; // how many bytes of content we've sent
clock_t ConnectionTime; // time a socket connection occurred
int Version; // server version
int Caps; // server capabilities
int Reconnect; // flag for the reconnection algorithm
int ReconnectTime; // value used in conjunction with the reconnection algorithm
int Succeeded; // had at least one successful connection (for reconnection alg.) -1 = password failure
int last_state; // using this as a means to allow for closing on errors but able to show a better message
int Switching; // if we're doing an automatic protocol version change (from v2 to v1)
char *ErrorMsg;
time_t ConnectedAt;
int meta_cached;
int art_cached[2];
unsigned short art_index[2];
unsigned short art_cached_span[2];
int art_cached_length[2];
// metadata information about the stream, etc
wchar_t *Title;
std::vector<std::wstring> NextList;
wchar_t *Song;
wchar_t *Album;
wchar_t *Artist;
wchar_t *Genre;
wchar_t *Comment;
wchar_t *Year;
void *APIC[2];
int APICLength[2];
int APICType[2];
T_OUTPUT_INFO() : BytesSent(0), ConnectionTime(0), Version(0),
Caps(0), Reconnect(0), ReconnectTime(0),
Succeeded(0), last_state(0), Switching(0),
ErrorMsg(0), ConnectedAt(0), meta_cached(0),
Title(0), Song(0), Album(0), Artist(0),
Genre(0), Comment(0), Year(0)
{
memset(art_cached, 0, sizeof(int) * 2);
memset(art_index, 0, sizeof(unsigned short) * 2);
memset(art_cached_span, 0, sizeof(unsigned short) * 2);
memset(art_cached_length, 0, sizeof(int) * 2);
memset(APIC, 0, sizeof(void *) * 2);
memset(APICLength, 0, sizeof(int) * 2);
memset(APICType, 0, sizeof(int) * 2);
}
~T_OUTPUT_INFO()
{
if (Title)
{
free(Title);
Title = 0;
}
if (Song)
{
free(Song);
Song = 0;
}
if (Album)
{
free(Album);
Album = 0;
}
if (Artist)
{
free(Artist);
Artist = 0;
}
if (Genre)
{
free(Genre);
Genre = 0;
}
if (Comment)
{
free(Comment);
Comment = 0;
}
if (Year)
{
free(Year);
Year = 0;
}
if (Succeeded == -2 && ErrorMsg) {
free(ErrorMsg);
ErrorMsg = 0;
}
}
};
struct T_OUTPUT {
int Connection; // using this for the title update callback so the correct instance is updated
void (*TitleCallback)(const int Connection, const int Mode);
api_connection *Output;
enum OUTPUTTYPE Type;
int Bitrate; // this shouldn't be here, but it's the only way we can tell the shoutcast server the bitrate
char *ContentType; // neither should this
int SlowClose; // set to 1 to wait until all data is sent before closing the connection
T_OUTPUT_CONFIG *Config;
T_OUTPUT_INFO Info;
T_OUTPUT() : Connection(0), TitleCallback(0), Output(0), Type(OUTTYPE_SOURCE), Bitrate(0), ContentType(0), SlowClose(0), Config(0) {}
};
enum OUTPUTSTATE {
OUT_ERROR, // not a true state, but is returned when GetState() is called with an invalid connection handle
OUT_DISCONNECTED,
OUT_CONNECT,
OUT_REQUEST_CIPHER,
OUT_RECV_CIPHER,
OUT_SENDAUTH,
OUT_RECVAUTHRESPONSE,
OUT_SEND_MIME,
OUT_RECV_MIME,
OUT_SEND_BITRATE,
OUT_RECV_BITRATE,
OUT_SEND_BUFSIZE,
OUT_RECV_BUFSIZE,
OUT_SEND_MAX,
OUT_RECV_MAX,
OUT_SENDYP,
OUT_SEND_INITFLUSH,
OUT_RECV_INITFLUSH,
OUT_SEND_INITSTANDBY,
OUT_RECV_INITSTANDBY,
/*OUT_SEND_INTRO,
OUT_RECV_INTRO,
OUT_SEND_BACKUP,
OUT_RECV_BACKUP,*/
OUT_SENDCONTENT,
OUT_DISCONNECT,
OUT_RECONNECT,
OUT_TITLESENDUPDATE,
OUT_FAIL_CIPHER,
OUT_SEND_METADATA,
OUT_SEND_ARTWORK,
};
static void *mutex;
class SHOUTCAST_OUTPUT {
private:
C_ENCODER *Encoder;
int IOwnEncoder;
C_SERIAL_JOBMANAGER<T_OUTPUT> OutputManager;
T_OUTPUT_TITLE metadata;
protected:
static int Output_Disconnected(int state, int last_state, T_OUTPUT *userData);
static int Output_Connect(int state, int last_state, T_OUTPUT *userData);
static int Output_Request_Cipher(int state, int last_state, T_OUTPUT *userData);
static int Output_Receive_Cipher(int state, int last_state, T_OUTPUT *userData);
static int Output_SendAuth(int state, int last_state, T_OUTPUT *userData);
static int Output_RecvAuthResponse(int state, int last_state, T_OUTPUT *userData);
static int Output_Send_Mime(int state, int last_state, T_OUTPUT *userData);
static int Output_Recv_Mime(int state, int last_state, T_OUTPUT *userData);
static int Output_Send_Bitrate(int state, int last_state, T_OUTPUT *userData);
static int Output_Recv_Bitrate(int state, int last_state, T_OUTPUT *userData);
static int Output_Send_Buf_Size(int state, int last_state, T_OUTPUT *userData);
static int Output_Recv_Buf_Size(int state, int last_state, T_OUTPUT *userData);
static int Output_Send_Max_Size(int state, int last_state, T_OUTPUT *userData);
static int Output_Recv_Max_Size(int state, int last_state, T_OUTPUT *userData);
static int Output_DUMMY(int state, int last_state, T_OUTPUT *userData);
static int Output_SendYP(int state, int last_state, T_OUTPUT *userData);
static int Output_Send_InitFlush(int state, int last_state, T_OUTPUT *userData);
static int Output_Recv_InitFlush(int state, int last_state, T_OUTPUT *userData);
static int Output_Send_InitStandby(int state, int last_state, T_OUTPUT *userData);
static int Output_Recv_InitStandby(int state, int last_state, T_OUTPUT *userData);
/*static int Output_Send_Intro(int state, int last_state, T_OUTPUT *userData);
static int Output_Recv_Intro(int state, int last_state, T_OUTPUT *userData);
static int Output_Send_Backup(int state, int last_state, T_OUTPUT *userData);
static int Output_Recv_Backup(int state, int last_state, T_OUTPUT *userData);*/
static int Output_SendContent(int state, int last_state, T_OUTPUT *userData);
static int Output_Disconnect(int state, int last_state, T_OUTPUT *userData);
static int Output_Reconnect(int state, int last_state, T_OUTPUT *userData);
static int Output_Title_SendUpdate(int state, int last_state, T_OUTPUT *userData);
static int Output_Send_Metadata(int state, int last_state, T_OUTPUT *userData);
static int Output_Send_Artwork(int state, int last_state, T_OUTPUT *userData);
// uvox21
static void createUvoxFrame(int length, char *payload_in, int type, T_OUTPUT *userData);
static int createUvoxMetaFrame(int length, char *payload_in, int type,
T_OUTPUT *userData, unsigned short id, unsigned short span = 1);
static int parseUvoxFrame(char *payload_in, char *payload_out);
static int checkUvoxFrameForError(char *pload_out, int state, T_OUTPUT *userData);
void (*lame_init)(void);
void (*lame_init_params)(lame_global_flags *);
int (*lame_encode_buffer_interleaved)(lame_global_flags *, short int pcm[], int num_samples, char *mp3buffer, int mp3buffer_size);
int (*lame_encode_flush)(lame_global_flags *, char *mp3buffer, int size);
HINSTANCE libinst;
public:
SHOUTCAST_OUTPUT();
void SetLame(void *init, void *params, void *encode, void *finish);
~SHOUTCAST_OUTPUT();
int Run(int mode = 0, void *Input = NULL, int InputSize = 0, int SaveEncoder = -1);
int AddOutput(int Connection, T_OUTPUT_CONFIG *Config, void (*TitleCallback)(const int Connection, const int Mode)=0);
void UpdateOutput(int Connection);
void RemoveOutput(int Connection);
int ConnectOutput(int Connection);
int DisconnectOutput(int Connection, int withReconnect = 0, int reconnectTime = -1); // withReconnect of -1 will use the output config's setting
void SetEncoder(C_ENCODER *encoder, int takeOwnership = 0);
// we will attempt to cache the title information to save on duplication and
// also to make it easier for the title to be re-sent on server disconnect
void UpdateTitleCache(wchar_t *Title, std::vector<std::wstring> NextList, wchar_t *Song,
wchar_t *Album, wchar_t *Artist, wchar_t *Genre, wchar_t *Comment,
wchar_t* Year, int Connection, bool sendNext);
void UpdateTitle(wchar_t *Title, std::vector<std::wstring> NextList,
int Connection, bool sendNext, bool UseCache = true);
void UpdateArtwork(int Connection);
void UpdateAlbumArtCache(void* APIC, int APIClength, int APICType, int Connection);
int UpdateAlbumArt(int Connection);
enum OUTPUTSTATE GetState(int Connection);
T_OUTPUT_CONFIG *operator[](int Connection);
T_OUTPUT_CONFIG *GetOutput(int Connection);
C_ENCODER *GetEncoder();
T_OUTPUT_INFO *GetOutputInfo(int Connection);
};
static unsigned short mid = 1;
#ifdef _DEBUG
#define DEBUG_STATE OutputDebugString(__FUNCTION__); OutputDebugString("\r\n");
#else
#define DEBUG_STATE
#endif
#define STATE userData->Info.last_state = last_state
#define LOCK if(WaitForSingleObject(mutex,INFINITE) == WAIT_OBJECT_0)
#define UNLOCK ReleaseMutex(mutex);
extern char sourceVersion[64];
extern HWND hMainDLG;
#endif // !__SHOUTCAST_OUTPUT_H__