//------------------------------------------------------------ bool ofxSoundFile::mpg123ReadFile(ofSoundBuffer & buffer){ size_t done=0; size_t block_size = mpg123_outblock( mp3File ); int err; if(buffer.size()==0){ buffer.resize(block_size); do{ err = mpg123_read(mp3File,(unsigned char*)&buffer[buffer.size()-block_size],block_size*4,&done); buffer.resize(buffer.size()+block_size); }while(err==MPG123_OK); buffer.resize(buffer.size()-(block_size-done/4)); if(err != MPG123_DONE){ ofLogError() << "Warning: Decoding ended prematurely because: " << (err == MPG123_ERR ? mpg123_strerror(mp3File) : mpg123_plain_strerror(err)); return false; } duration = float(buffer.size())/float(channels) / float(samplerate); }else{ err = mpg123_read(mp3File,(unsigned char*)&buffer[0],buffer.size()*sizeof(float),&done); if(err != MPG123_OK){ ofLogError() << "Warning: Error decoding mp3: " << (err == MPG123_ERR ? mpg123_strerror(mp3File) : mpg123_plain_strerror(err)); return false; } } return true; }
bool UMP3Decoder::Decode(TArray<uint8> &OutBuffer) { check(OutBuffer.Num() == 0); check(OutBuffer.GetAllocatedSize() >= WAV_HEADER_SIZE); OutBuffer.AddZeroed(WAV_HEADER_SIZE / OutBuffer.GetTypeSize()); FDateTime tStart = FDateTime::Now(); unsigned char* BlockBuffer = (unsigned char*)FMemory::Malloc(BlockBufferSize); size_t bytesRead = 0; size_t done = 0; int result; do { result = mpg123_read(Handle, BlockBuffer, BlockBufferSize, &done); bytesRead += done; OutBuffer.Append(BlockBuffer, done); } while (result == MPG123_OK); uint8 header[WAV_HEADER_SIZE]; WriteWaveHeader(header, bytesRead, Samplerate, Channels); FMemory::Memcpy(OutBuffer.GetData(), header, WAV_HEADER_SIZE); FMemory::Free(BlockBuffer); SizeInBytes = bytesRead; bool bSuccess = result == MPG123_OK || result == MPG123_DONE; UE_LOG(MP3ImporterLog, Display, TEXT("Decoding finished. %s bytes in %d ms. Success: %s"), *FString::FormatAsNumber((int32)bytesRead), (int32)(FDateTime::Now() - tStart).GetTotalMilliseconds(), bSuccess ? TEXT("True") : TEXT("False")); return bSuccess; }
int MP3InputStream::decodeFrames(int frames, void* samples) { int ret, rret; unsigned int len = frames * GetFrameSize(this); unsigned char *out = (unsigned char*)samples; size_t rb = 0, rv = 0; if (!mh) return 0; while (len > 0) { ret = mpg123_read(mh, out, len, &rv); rb += rv; out += rv; len -= rv; if (ret == MPG123_NEED_MORE) { // read the data m_input_length = m_file->read(m_input_buffer, INPUT_BUFFER_SIZE); if (m_input_length == 0) { m_eof = true; return true; } // feed it to mpg123 rret = mpg123_feed(mh,m_input_buffer,m_input_length); if (rret != MPG123_OK) return false; // something went wrong! } } return rb / GetFrameSize(this); }
int check_sample(struct seeko *so, size_t i) { short buf[2]; /* at max one left and right sample */ size_t got = 0; if(mpg123_seek(m, so->position[i], SEEK_SET) != so->position[i]) { printf("Error seeking to %"OFF_P": %s\n", (off_p)so->position[i], mpg123_strerror(m)); return -1; } if( mpg123_read(m, (unsigned char*)buf, channels*sizeof(short), &got) != MPG123_OK || got/sizeof(short) != channels ) { printf("Error occured on reading sample %"SIZE_P"! (%s)\n", (size_p)i, mpg123_strerror(m)); return -1; } if(buf[0] == so->left[i] && (channels == 1 || buf[1] == so->right[i])) { printf("sample %"SIZE_P" PASS\n", (size_p)i); } else { if(channels == 1) printf("sample %"SIZE_P" FAIL (%i != %i)\n", (size_p)i, buf[0], so->left[i]); else printf("sample %"SIZE_P" FAIL (%i != %i || %i != %i)\n", (size_p)i, buf[0], so->left[i], buf[1], so->right[i]); return -1; } return 0; }
VALUE rb_mpg123_read(VALUE self, VALUE _size) { int size = FIX2INT(_size); float *buffer = malloc(size * sizeof(float)); VALUE result = rb_ary_new2(size); mpg123_handle *mh = NULL; size_t done = 0; int i; int err = MPG123_OK; Data_Get_Struct(self, mpg123_handle, mh); err = mpg123_read(mh, (unsigned char *) buffer, size * sizeof(float), &done); if (err == MPG123_OK || err == MPG123_DONE) { for (i = 0; i < size; i++) { rb_ary_store(result, i, rb_float_new(buffer[i])); } free(buffer); return result; } else { free(buffer); rb_raise(rb_eStandardError, "%s", mpg123_plain_strerror(err)); } }
MP3Decoder::MP3Decoder(const char* filename) { f = fopen(filename,"rb"); fseek(f,0,SEEK_END); size_t size = ftell(f); cbuf = (uchar*)malloc(size*24); size_t buffer_size = 0; size_t done = 0; rate = 0; channels = 0; encoding = 0; int err = MPG123_OK; err = mpg123_init(); if(err != MPG123_OK || (mh = mpg123_new(NULL, &err)) == NULL) { QMessageBox::critical(0,QString("MP3 decoding error!"),QString("Basic setup goes wrong: ")+QString(mpg123_plain_strerror(err))); cleanup(mh); return ; } /* Let mpg123 work with the file, that excludes MPG123_NEED_MORE messages. */ if( mpg123_open(mh, filename) != MPG123_OK /* Peek into track and get first output format. */ || mpg123_getformat(mh, &rate, &channels, &encoding) != MPG123_OK ) { QMessageBox::critical(0,QString("MP3 decoding error!"),QString("Error decoding mp3 file: ")+QString(mpg123_strerror(mh))); cleanup(mh); return ; } if(encoding != MPG123_ENC_SIGNED_16 && encoding != MPG123_ENC_FLOAT_32) { /* Signed 16 is the default output format anyways; it would actually by only different if we forced it. So this check is here just for this explanation. */ cleanup(mh); QMessageBox::critical(0,QString("MP3 decoding error!"),"Bad encoding: 0x"+QString::number(encoding,16)); return; } /* Ensure that this output format will not change (it could, when we allow it). */ mpg123_format_none(mh); mpg123_format(mh, rate, channels, encoding); buffer_size = mpg123_outblock(mh); cdone=0; buffer = (unsigned char*)malloc( buffer_size ); do { err = mpg123_read( mh, buffer, buffer_size, &done ); if(done<1)qDebug()<<"Error while reading mp3: "<<mpg123_errcode(mh); else memcpy(cbuf+cdone,buffer,done); cdone+=done; } while (err==MPG123_OK); cbuf[cdone-1]=0; if(err != MPG123_DONE){ fprintf( stderr, "Warning: Decoding ended prematurely because: %s\n", err == MPG123_ERR ? mpg123_strerror(mh) : mpg123_plain_strerror(err) ); QMessageBox::critical(0,QString("MP3 decoding error!"),QString("MP3 Decoding ended prematurely: %1").arg(err == MPG123_ERR ? mpg123_strerror(mh) : mpg123_plain_strerror(err) )); return; } }
int Waveform::GetTrackSize(mpg123_handle *mh,int bits, int channels) { size_t buffer_size; unsigned char *buffer; size_t done; int trackSize=0; int fileSize=0; if(mpg123_length(mh) > 0) { return mpg123_length(mh); } buffer_size = mpg123_outblock(mh); buffer = (unsigned char*) malloc(buffer_size * sizeof(unsigned char)); mpg123_seek(mh,0,SEEK_SET); for (fileSize = 0 ; mpg123_read(mh, buffer, buffer_size, &done) == MPG123_OK ; ) { fileSize += done; } free(buffer); trackSize = fileSize/(bits*channels); return trackSize; }
//------------------------------------------------------------ bool ofOpenALSoundPlayer_TimelineAdditions::mpg123ReadFile(string path,vector<short> & buffer,vector<float> & fftAuxBuffer){ int err = MPG123_OK; mpg123_handle * f = mpg123_new(NULL,&err); if(mpg123_open(f,path.c_str())!=MPG123_OK){ ofLog(OF_LOG_ERROR,"ofOpenALSoundPlayer_TimelineAdditions: couldnt read " + path); return false; } int encoding; long int rate; mpg123_getformat(f,&rate,&channels,&encoding); if(encoding!=MPG123_ENC_SIGNED_16){ ofLog(OF_LOG_ERROR,"ofOpenALSoundPlayer_TimelineAdditions: unsupported encoding"); return false; } samplerate = rate; size_t done=0; size_t buffer_size = mpg123_outblock( f ); buffer.resize(buffer_size/2); while(mpg123_read(f,(unsigned char*)&buffer[buffer.size()-buffer_size/2],buffer_size,&done)!=MPG123_DONE){ buffer.resize(buffer.size()+buffer_size/2); }; buffer.resize(buffer.size()-(buffer_size/2-done/2)); mpg123_close(f); mpg123_delete(f); fftAuxBuffer.resize(buffer.size()); for(int i=0;i<(int)buffer.size();i++){ fftAuxBuffer[i] = float(buffer[i])/32565.f; } duration = float(buffer.size()/channels) / float(samplerate); return true; }
void CALLBACK WaveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { DWORD readBytes = 0; LPWAVEHDR pWaveHdr; LPSTR p; switch(uMsg) { case WOM_DONE: if(playingFlag == FALSE) { return; } pWaveHdr = (LPWAVEHDR)dwParam1; p = pWaveHdr->lpData; EnterCriticalSection(&cs); mpg123_read(mh, p, SOUND_BUFFER_LEN, &readBytes); LeaveCriticalSection(&cs); if(readBytes == 0) { playingFlag = FALSE; return; } waveOutUnprepareHeader(hWaveOut, pWaveHdr, sizeof(WAVEHDR)); ZeroMemory(pWaveHdr, sizeof(WAVEHDR)); pWaveHdr->dwBufferLength = readBytes; pWaveHdr->lpData = p; waveOutPrepareHeader(hWaveOut, pWaveHdr, sizeof(WAVEHDR)); waveOutWrite(hWaveOut, pWaveHdr, sizeof(WAVEHDR)); break; } }
int Mpg123Decoder::decode() { int size = 0; while (size < bufferSize && !eof) { size_t numbytes = 0; int res = mpg123_read(handle, (unsigned char *) buffer + size, bufferSize - size, &numbytes); switch (res) { case MPG123_NEED_MORE: case MPG123_NEW_FORMAT: case MPG123_OK: size += numbytes; continue; case MPG123_DONE: size += numbytes; eof = true; default: return size; } } return size; }
//------------------------------------------------------------ bool ofOpenALSoundPlayer::mpg123ReadFile(string path,vector<short> & buffer,vector<float> & fftAuxBuffer){ int err = MPG123_OK; mpg123_handle * f = mpg123_new(NULL,&err); if(mpg123_open(f,path.c_str())!=MPG123_OK){ ofLogError("ofOpenALSoundPlayer") << "mpg123ReadFile(): couldn't read \"" << path << "\""; return false; } mpg123_enc_enum encoding; long int rate; mpg123_getformat(f,&rate,&channels,(int*)&encoding); if(encoding!=MPG123_ENC_SIGNED_16){ ofLogError("ofOpenALSoundPlayer") << "mpg123ReadFile(): " << getMpg123EncodingString(encoding) << " encoding for \"" << path << "\"" << " unsupported, expecting MPG123_ENC_SIGNED_16"; return false; } samplerate = rate; size_t done=0; size_t buffer_size = mpg123_outblock( f ); buffer.resize(buffer_size/2); while(mpg123_read(f,(unsigned char*)&buffer[buffer.size()-buffer_size/2],buffer_size,&done)!=MPG123_DONE){ buffer.resize(buffer.size()+buffer_size/2); }; buffer.resize(buffer.size()-(buffer_size/2-done/2)); mpg123_close(f); mpg123_delete(f); fftAuxBuffer.resize(buffer.size()); for(int i=0;i<(int)buffer.size();i++){ fftAuxBuffer[i] = float(buffer[i])/32565.f; } duration = float(buffer.size()/channels) / float(samplerate); return true; }
static bool SYS_StreamNextBuffer(int bufferIndex, SYS_Wave* wave) { assert(wave->mh); // TODO: should probably use a static buffer unsigned char* buffer = (unsigned char*)malloc( wave->bufferSize ); size_t bytes_read = 0; int error = mpg123_read(wave->mh, buffer, wave->bufferSize, &bytes_read); // We are not in feeder mode, so MPG123_OK, MPG123_ERR and MPG123_NEW_FORMAT are the only possibilities. // We do not handle a new format, MPG123_DONE is the end... so abort on anything not MPG123_OK. //} while (error == MPG123_OK); if ( error != MPG123_OK ) { // we hit the end. return false; // TODO: loop or stop the sound... } #ifdef SYS_SOUNDDEBUG printf("bytes_read = %d\n", (unsigned int)bytes_read); #endif // copy PCM data into OpenAL buffer alBufferData(wave->mp3_buffers[bufferIndex], AL_FORMAT_STEREO16, buffer, bytes_read, wave->rate); CheckForErrors(); free(buffer); return true; }
bool Mpg123Decoder::IsMp3(FILE* stream) { Mpg123Decoder decoder; // Prevent stream handle destruction mpg123_replace_reader_handle(decoder.handle.get(), custom_read, custom_seek, noop_close); // Prevent skipping of too many garbage, breaks heuristic mpg123_param(decoder.handle.get(), MPG123_RESYNC_LIMIT, 64, 0.0); if (!decoder.Open(stream)) { return false; } unsigned char buffer[1024]; int err = 0; size_t done = 0; int err_count = 0; // Read beginning of assumed MP3 file and count errors as an heuristic to detect MP3 for (int i = 0; i < 10; ++i) { err = mpg123_read(decoder.handle.get(), buffer, 1024, &done); if (err != MPG123_OK) { err_count += 1; } if (err_count >= 3) { break; } } return err_count < 3; }
void mpgplayer::run() { if(mpg123_init() != MPG123_OK) qDebug("Error initilizing mpg123"); const char **test = mpg123_supported_decoders(); int error; mpg123_handle *mh = mpg123_new(test[0],&error); if(!mpg123_feature(MPG123_FEATURE_DECODE_LAYER3)) { qDebug("You do not seem to have mp3 decoding support"); return; } mpg123_format_none(mh); if(mpg123_format(mh,samplerate,MPG123_STEREO,MPG123_ENC_SIGNED_16)!=MPG123_OK) qDebug("Error in initilizing format decoder"); qDebug(test[0]); mpg123_open(mh,"/home/eli/Projects/groove-evan/Animal.mp3"); net = TData; pa_simple *s; pa_sample_spec ss; ss.format = PA_SAMPLE_S16NE; ss.rate = samplerate; ss.channels = 2; s =pa_simple_new(NULL,"Groove",PA_STREAM_PLAYBACK ,NULL,"Music",&ss,NULL,NULL,NULL); unsigned char bytes[1024]; size_t bsize = 1024; size_t done = 0; bool stop = false; playing=true; while(!stop) { switch(net) { case TWait: usleep(100); break; case TData: if(mpg123_read(mh,bytes,bsize,&done)==MPG123_DONE) { net=TFinish; } pa_simple_write(s,bytes,done,&error); break; case TAbort: stop = true; break; case TFinish: pa_simple_drain(s,&error); stop = true; break; default: break; } } qDebug("Finsihed playback"); pa_simple_free(s); mpg123_exit(); }
size_t AudioStreamerMpg123::read_impl(unsigned char* buffer, size_t size) { mpg123_handle* mh = static_cast<mpg123_handle*>(handle_); size_t done; int err = mpg123_read(mh, buffer, size, &done); return done; }
/** * @name Play MP3 * Questa funzione si occupa di riprodurre un frame Audio */ int playMp3(){ /* decode and play */ if (isPaused==0 && mpg123_read(mh, buffer, buffer_size, &done) == MPG123_OK) { writeAudio((char*)buffer,done); } else { if (isPaused==0) return 2; } return 0; }
qint64 DecoderMPG123::read(unsigned char *data, qint64 size) { size_t done = 0; int err = mpg123_read(m_handle, data, size, &done); if(err != MPG123_DONE && err != MPG123_OK) { qWarning("DecoderMPG123: decoder error: %s", mpg123_plain_strerror(err)); return -1; } mpg123_info(m_handle, &m_frame_info); return done; }
int FileFormatMP3::FillBuffer(unsigned char* buffer, int numBytes) { size_t numDone = 0; int err = MPG123_OK; err = mpg123_read(_mpg123, buffer, numBytes, &numDone); _filePosition += numDone; if( numDone == 0 || err == MPG123_DONE ) { return -1; } return numDone; }
int MusicPlayer::pullAudio(AUDIO_SAMPLE_TYPE *target, int sampleCount) { memset(target, 0, sampleCount * sizeof(AUDIO_SAMPLE_TYPE)); #ifdef USE_OGG if(v && error==0) { int samples = stb_vorbis_get_samples_short_interleaved(v, channels, target, sampleCount); if(samples==0) { stb_vorbis_seek_start(v); stb_vorbis_get_samples_short_interleaved(v, channels, target, sampleCount); // error=-1; } } #endif #ifdef USE_MP3 if (!error) { size_t done; error = mpg123_read(mh, (unsigned char *) target, sampleCount * sizeof(AUDIO_SAMPLE_TYPE), &done); // read will return MPG123_DONE eventually... if (error == MPG123_DONE) { mpg123_seek(mh, 0, SEEK_SET); error = mpg123_read(mh, (unsigned char *) target, sampleCount * sizeof(AUDIO_SAMPLE_TYPE), &done); } // ignore format change if (error == MPG123_NEW_FORMAT) error = MPG123_OK; } #endif return sampleCount; }
static inline int readBuffer( Mp3File* mp3 ) { size_t done = 0; int err = mpg123_read( mp3->handle, mp3->buffer, mp3->buffer_size, &done ); mp3->leftSamples = done / 2; mp3->offset = 0; if( err != MPG123_OK ) return 0; else return done; }
static inline int readBuffer(MP3File* mp3) { size_t done = 0; int err = mpg123_read(mp3->handle, mp3->buffer, mp3->buffer_size, &done); mp3->leftSamples = done / 2; mp3->offset = 0; if (err != MPG123_OK) __android_log_write(ANDROID_LOG_ERROR, "podax-jni", mpg123_strerror(mp3->handle)); return err != MPG123_OK ? 0 : done; }
inline int MP3Decoder::readBuffer() { size_t done = 0; int err = mpg123_read(mp3File.handle, mp3File.buffer, mp3File.buffer_size, &done); mp3File.leftSamples = done/2; mp3File.offset = 0; if( err != MPG123_OK) { printf("file not read into buffer correctly/n"); return 0; } else return done; }
static int S_MP3_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer) { mp3_priv_t *priv = (mp3_priv_t *) stream->priv; size_t bytes_read = 0; int res = mpg123_read (priv->handle, (unsigned char *)buffer, (size_t)bytes, &bytes_read); switch (res) { case MPG123_DONE: Con_DPrintf("mp3 EOF\n"); case MPG123_OK: return (int)bytes_read; } return -1; /* error */ }
size_t gaudio_Mp3Read(g_id gid, size_t size, void *data) { GGMp3Handle *handle = (GGMp3Handle*)gid; size = (size / handle->sampleSize) * handle->sampleSize; size_t done; int err = mpg123_read(handle->mh, (unsigned char*)data, size, &done); if (err == MPG123_OK || err == MPG123_DONE) return done; return 0; }
ALuint Mpg123Decoder::read(ALvoid *ptr, ALuint count) { ALuint total = 0; while(total < count) { size_t got = 0; int ret = mpg123_read(mMpg123, reinterpret_cast<unsigned char*>(ptr), count*mChannels*2, &got); if((ret != MPG123_OK && ret != MPG123_DONE) || got == 0) break; total += got / mChannels / 2; if(ret == MPG123_DONE) break; } return total; }
//read to ->buffer static cxInt cxMp3StreamRead(cxAny stream,cxPointer buffer,cxInt size) { cxMp3Stream this = stream; if(!this->super.canRead){ return 0; } size_t done = 0; cxInt rv = mpg123_read(this->mh, buffer, size, &done); //error if(rv < 0 && rv != MPG123_DONE){ CX_ERROR("read mp3 stream error"); return 0; } return done; }
//------------------------------------------------------------ bool ofOpenALSoundPlayer::mpg123Stream(string path,vector<short> & buffer,vector<float> & fftAuxBuffer){ if(!mp3streamf){ int err = MPG123_OK; mp3streamf = mpg123_new(NULL,&err); if(mpg123_open(mp3streamf,path.c_str())!=MPG123_OK){ mpg123_close(mp3streamf); mpg123_delete(mp3streamf); ofLogError("ofOpenALSoundPlayer") << "mpg123Stream(): couldn't read \"" << path << "\""; return false; } long int rate; mpg123_getformat(mp3streamf,&rate,&channels,(int*)&stream_encoding); if(stream_encoding!=MPG123_ENC_SIGNED_16){ ofLogError("ofOpenALSoundPlayer") << "mpg123Stream(): " << getMpg123EncodingString(stream_encoding) << " encoding for \"" << path << "\"" << " unsupported, expecting MPG123_ENC_SIGNED_16"; return false; } samplerate = rate; mp3_buffer_size = mpg123_outblock( mp3streamf ); mpg123_seek(mp3streamf,0,SEEK_END); off_t samples = mpg123_tell(mp3streamf); duration = float(samples/channels) / float(samplerate); mpg123_seek(mp3streamf,0,SEEK_SET); } int curr_buffer_size = mp3_buffer_size; if(speed>1) curr_buffer_size *= (int)round(speed); buffer.resize(curr_buffer_size); fftAuxBuffer.resize(buffer.size()); size_t done=0; if(mpg123_read(mp3streamf,(unsigned char*)&buffer[0],curr_buffer_size*2,&done)==MPG123_DONE){ setPosition(0); buffer.resize(done/2); fftAuxBuffer.resize(done/2); if(!bLoop) stopThread(); stream_end = true; } for(int i=0;i<(int)buffer.size();i++){ fftAuxBuffer[i] = float(buffer[i])/32565.f; } return true; }
// Returns samples (1 sample contains data from all channels) long Mp3Stream::read(float* buffer, int bytes) { LOG4CXX_TRACE(narratorMP3StreamLog, "read " << bytes << " bytes from mp3 file"); // mpg123 uses enconding MPG123_ENC_SIGNED_16 which results in decoded short samples short *shortBuffer; shortBuffer = new short[bytes * mChannels]; size_t done = 0; int result = mpg123_read(mh, (unsigned char*)shortBuffer, bytes*sizeof(short)*mChannels, &done); switch (result) { case MPG123_DONE: LOG4CXX_DEBUG(narratorMP3StreamLog, "End of stream"); break; case MPG123_OK: break; } LOG4CXX_TRACE(narratorMP3StreamLog, done << " bytes decoded"); // convert short buffer to scaled float buffer float *bufptr = buffer; for (int i = 0; i < done/sizeof(short); i++) { int value = (int)shortBuffer[i]; if (value == 0) { *bufptr++ = 0.f; } else if (value < 0) { // multiple with 2.0f to increase volume by a factor of 2 (+6dB) *bufptr++ = (float)(value/scaleNegative) * 2.0f; } else { // multiple with 2.0f to increase volume by a factor of 2 (+6dB) *bufptr++ = (float)(value/scalePositive) * 2.0f; } } delete shortBuffer; return done/(sizeof(short) * mChannels); }
int stream_read(stream *stream, void *buffer, size_t buffer_size) { size_t done; int err = 0; err = mpg123_read(stream->mpg123, buffer, buffer_size, &done); if (err == MPG123_DONE) { Pa_StopStream(stream->pa_stream); } if (err != MPG123_OK) { memset(buffer, 0, buffer_size); } return err; }
size_t Mpg123Input::readFrames() { if (!handle) { return 0; } size_t numberRead; int result = mpg123_read(handle->mpg123, (unsigned char*) handle->buffer, (size_t) handle->rate * (size_t) handle->channels * sizeof(float), &numberRead); if (MPG123_OK!=result && MPG123_DONE!=result) { return 0; } numberRead /= (size_t) handle->channels * sizeof(float); return numberRead; }