00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016 #include "global.hpp"
00017
00018 #include "config.hpp"
00019 #include "filesystem.hpp"
00020 #include "foreach.hpp"
00021 #include "game_preferences.hpp"
00022 #include "log.hpp"
00023 #include "serialization/string_utils.hpp"
00024 #include "sound.hpp"
00025 #include "sound_music_track.hpp"
00026 #include "util.hpp"
00027
00028 #include "SDL_mixer.h"
00029
00030 #include <list>
00031
00032 static lg::log_domain log_audio("audio");
00033 #define LOG_AUDIO LOG_STREAM(info, log_audio)
00034 #define ERR_AUDIO LOG_STREAM(err, log_audio)
00035
00036 namespace sound {
00037
00038 std::vector<Mix_Chunk*> channel_chunks;
00039
00040
00041
00042 std::vector<int> channel_ids;
00043
00044 static void play_sound_internal(const std::string& files, channel_group group, unsigned int repeats=0,
00045 unsigned int distance=0, int id=-1, int loop_ticks=0, int fadein_ticks=0);
00046 }
00047
00048 namespace {
00049
00050 bool mix_ok = false;
00051 int music_start_time = 0;
00052 unsigned music_refresh = 0;
00053 unsigned music_refresh_rate = 20;
00054 bool want_new_music = false;
00055 int fadingout_time=5000;
00056 bool no_fading = false;
00057
00058
00059 const size_t n_of_channels = 16;
00060
00061
00062 const size_t bell_channel = 0;
00063 const size_t timer_channel = 1;
00064
00065
00066 const size_t source_channels = n_of_channels - 8;
00067 const size_t source_channel_start = timer_channel + 1;
00068 const size_t source_channel_last = source_channel_start + source_channels - 1;
00069 const size_t UI_sound_channel = source_channel_last + 1;
00070 const size_t n_reserved_channels = UI_sound_channel + 1;
00071
00072
00073
00074 #ifdef LOW_MEM
00075 unsigned max_cached_chunks = 64;
00076 #else
00077 unsigned max_cached_chunks = 256;
00078 #endif
00079
00080 std::map< Mix_Chunk*, int > chunk_usage;
00081
00082 }
00083
00084 static void increment_chunk_usage(Mix_Chunk* mcp) {
00085 ++(chunk_usage[mcp]);
00086 }
00087
00088 static void decrement_chunk_usage(Mix_Chunk* mcp) {
00089 if(mcp == NULL) return;
00090 std::map< Mix_Chunk*, int >::iterator this_usage = chunk_usage.find(mcp);
00091 assert(this_usage != chunk_usage.end());
00092 if(--(this_usage->second) == 0) {
00093 Mix_FreeChunk(mcp);
00094 chunk_usage.erase(this_usage);
00095 }
00096 }
00097
00098 namespace {
00099
00100 class sound_cache_chunk {
00101 public:
00102 sound_cache_chunk(const std::string& f) : group(sound::NULL_CHANNEL), file(f), data_(NULL) {}
00103 sound_cache_chunk(const sound_cache_chunk& scc)
00104 : group(scc.group), file(scc.file), data_(scc.data_)
00105 {
00106 increment_chunk_usage(data_);
00107 }
00108
00109 ~sound_cache_chunk()
00110 {
00111 decrement_chunk_usage(data_);
00112 }
00113
00114 sound::channel_group group;
00115 std::string file;
00116
00117 void set_data(Mix_Chunk* d) {
00118 increment_chunk_usage(d);
00119 decrement_chunk_usage(data_);
00120 data_ = d;
00121 }
00122
00123 Mix_Chunk* get_data() const {
00124 return data_;
00125 }
00126
00127 bool operator==(sound_cache_chunk const &scc) const {
00128 return file == scc.file;
00129 }
00130
00131 bool operator!=(sound_cache_chunk const &scc) const { return !operator==(scc); }
00132
00133 sound_cache_chunk& operator=(const sound_cache_chunk& scc) {
00134 file = scc.file;
00135 group = scc.group;
00136 set_data(scc.get_data());
00137 return *this;
00138 }
00139
00140 private:
00141 Mix_Chunk* data_;
00142 };
00143
00144 std::list< sound_cache_chunk > sound_cache;
00145 typedef std::list< sound_cache_chunk >::iterator sound_cache_iterator;
00146 std::map<std::string,Mix_Music*> music_cache;
00147
00148 std::vector<std::string> played_before;
00149
00150
00151
00152
00153
00154
00155
00156
00157
00158 std::vector<sound::music_track> current_track_list;
00159 sound::music_track current_track;
00160 sound::music_track last_track;
00161
00162 }
00163
00164 static bool track_ok(const std::string& id)
00165 {
00166 LOG_AUDIO << "Considering " << id << "\n";
00167
00168
00169
00170 if (id == current_track.file_path())
00171 return false;
00172
00173 if (current_track_list.size() <= 3)
00174 return true;
00175
00176
00177
00178
00179
00180
00181
00182
00183
00184 unsigned int num_played = 0;
00185 std::set<std::string> played;
00186 std::vector<std::string>::reverse_iterator i;
00187
00188 for (i = played_before.rbegin(); i != played_before.rend(); ++i) {
00189 if (*i == id) {
00190 ++num_played;
00191 if (num_played == 2)
00192 break;
00193 } else {
00194 played.insert(*i);
00195 }
00196 }
00197
00198
00199 if (num_played == 2 && played.size() != current_track_list.size() - 1) {
00200 LOG_AUDIO << "Played twice with only " << played.size()
00201 << " tracks between\n";
00202 return false;
00203 }
00204
00205
00206 i = played_before.rbegin();
00207 if (i != played_before.rend()) {
00208 ++i;
00209 if (i != played_before.rend()) {
00210 if (*i == id) {
00211 LOG_AUDIO << "Played just before previous\n";
00212 return false;
00213 }
00214 }
00215 }
00216
00217 return true;
00218 }
00219
00220
00221 static const sound::music_track &choose_track()
00222 {
00223 assert(!current_track_list.empty());
00224
00225 unsigned int track = 0;
00226
00227 if (current_track_list.size() > 1) {
00228 do {
00229 track = rand()%current_track_list.size();
00230 } while (!track_ok( current_track_list[track].file_path() ));
00231 }
00232
00233
00234 played_before.push_back( current_track_list[track].file_path() );
00235 return current_track_list[track];
00236 }
00237
00238 static std::string pick_one(const std::string &files)
00239 {
00240 std::vector<std::string> ids = utils::split(files);
00241
00242 if (ids.empty())
00243 return "";
00244 if (ids.size() == 1)
00245 return ids[0];
00246
00247 #ifdef LOW_MEM
00248
00249 return ids[0];
00250 #endif
00251
00252
00253 static std::map<std::string,unsigned int> prev_choices;
00254 unsigned int choice;
00255
00256 if (prev_choices.find(files) != prev_choices.end()) {
00257 choice = rand()%(ids.size()-1);
00258 if (choice >= prev_choices[files])
00259 ++choice;
00260 prev_choices[files] = choice;
00261 } else {
00262 choice = rand()%ids.size();
00263 prev_choices.insert(std::pair<std::string,unsigned int>(files,choice));
00264 }
00265
00266 return ids[choice];
00267 }
00268
00269 namespace {
00270
00271 struct audio_lock
00272 {
00273 audio_lock()
00274 {
00275 SDL_LockAudio();
00276 }
00277
00278 ~audio_lock()
00279 {
00280 SDL_UnlockAudio();
00281 }
00282 };
00283
00284 }
00285
00286
00287 namespace sound {
00288
00289
00290 static void channel_finished_hook(int channel)
00291 {
00292 channel_chunks[channel] = NULL;
00293 channel_ids[channel] = -1;
00294 }
00295
00296 bool init_sound() {
00297 LOG_AUDIO << "Initializing audio...\n";
00298 if(SDL_WasInit(SDL_INIT_AUDIO) == 0)
00299 if(SDL_InitSubSystem(SDL_INIT_AUDIO) == -1)
00300 return false;
00301
00302 if(!mix_ok) {
00303 if(Mix_OpenAudio(preferences::sample_rate(), MIX_DEFAULT_FORMAT, 2, preferences::sound_buffer_size()) == -1) {
00304 mix_ok = false;
00305 ERR_AUDIO << "Could not initialize audio: " << Mix_GetError() << "\n";
00306 return false;
00307 }
00308
00309 mix_ok = true;
00310 Mix_AllocateChannels(n_of_channels);
00311 Mix_ReserveChannels(n_reserved_channels);
00312
00313 channel_chunks.clear();
00314 channel_chunks.resize(n_of_channels, NULL);
00315 channel_ids.resize(n_of_channels, -1);
00316
00317 Mix_GroupChannel(bell_channel, SOUND_BELL);
00318 Mix_GroupChannel(timer_channel, SOUND_TIMER);
00319 Mix_GroupChannels(source_channel_start, source_channel_last, SOUND_SOURCES);
00320 Mix_GroupChannel(UI_sound_channel, SOUND_UI);
00321 Mix_GroupChannels(n_reserved_channels, n_of_channels - 1, SOUND_FX);
00322
00323 set_sound_volume(preferences::sound_volume());
00324 set_UI_volume(preferences::UI_volume());
00325 set_music_volume(preferences::music_volume());
00326 set_bell_volume(preferences::bell_volume());
00327
00328 Mix_ChannelFinished(channel_finished_hook);
00329
00330 LOG_AUDIO << "Audio initialized.\n";
00331
00332 play_music();
00333 }
00334 return true;
00335 }
00336
00337 void close_sound() {
00338 int frequency, channels;
00339 Uint16 format;
00340
00341 if(mix_ok) {
00342 stop_bell();
00343 stop_UI_sound();
00344 stop_sound();
00345 sound_cache.clear();
00346 stop_music();
00347 mix_ok = false;
00348
00349 int numtimesopened = Mix_QuerySpec(&frequency, &format, &channels);
00350 if(numtimesopened == 0) {
00351 ERR_AUDIO << "Error closing audio device: " << Mix_GetError() << "\n";
00352 }
00353 while (numtimesopened) {
00354 Mix_CloseAudio();
00355 --numtimesopened;
00356 }
00357 }
00358 if(SDL_WasInit(SDL_INIT_AUDIO) != 0)
00359 SDL_QuitSubSystem(SDL_INIT_AUDIO);
00360
00361 LOG_AUDIO << "Audio device released.\n";
00362 }
00363
00364 void reset_sound() {
00365 bool music = preferences::music_on();
00366 bool sound = preferences::sound_on();
00367 bool UI_sound = preferences::UI_sound_on();
00368 bool bell = preferences::turn_bell();
00369
00370 if (music || sound || bell || UI_sound) {
00371 sound::close_sound();
00372 if (!sound::init_sound()) {
00373 ERR_AUDIO << "Error initializing audio device: " << Mix_GetError() << "\n";
00374 }
00375 if (!music)
00376 sound::stop_music();
00377 if (!sound)
00378 sound::stop_sound();
00379 if (!UI_sound)
00380 sound::stop_UI_sound();
00381 if (!bell)
00382 sound::stop_bell();
00383 }
00384 }
00385
00386 void stop_music() {
00387 if(mix_ok) {
00388 Mix_HaltMusic();
00389
00390 std::map<std::string,Mix_Music*>::iterator i;
00391 for(i = music_cache.begin(); i != music_cache.end(); ++i)
00392 Mix_FreeMusic(i->second);
00393 music_cache.clear();
00394 }
00395 }
00396
00397 void stop_sound() {
00398 if(mix_ok) {
00399 Mix_HaltGroup(SOUND_SOURCES);
00400 Mix_HaltGroup(SOUND_FX);
00401 sound_cache_iterator itor = sound_cache.begin();
00402 while(itor != sound_cache.end())
00403 {
00404 if(itor->group == SOUND_SOURCES || itor->group == SOUND_FX) {
00405 itor = sound_cache.erase(itor);
00406 } else {
00407 ++itor;
00408 }
00409 }
00410 }
00411 }
00412
00413
00414
00415
00416 void stop_bell() {
00417 if(mix_ok) {
00418 Mix_HaltGroup(SOUND_BELL);
00419 Mix_HaltGroup(SOUND_TIMER);
00420 sound_cache_iterator itor = sound_cache.begin();
00421 while(itor != sound_cache.end())
00422 {
00423 if(itor->group == SOUND_BELL || itor->group == SOUND_TIMER) {
00424 itor = sound_cache.erase(itor);
00425 } else {
00426 ++itor;
00427 }
00428 }
00429 }
00430 }
00431
00432 void stop_UI_sound() {
00433 if(mix_ok) {
00434 Mix_HaltGroup(SOUND_UI);
00435 sound_cache_iterator itor = sound_cache.begin();
00436 while(itor != sound_cache.end())
00437 {
00438 if(itor->group == SOUND_UI) {
00439 itor = sound_cache.erase(itor);
00440 } else {
00441 ++itor;
00442 }
00443 }
00444 }
00445 }
00446
00447 void play_music_once(const std::string &file)
00448 {
00449
00450 current_track_list.clear();
00451 current_track = music_track(file);
00452 play_music();
00453 }
00454
00455 void empty_playlist()
00456 {
00457 current_track_list.clear();
00458 }
00459
00460 void play_music()
00461 {
00462 music_start_time = 1;
00463 want_new_music=true;
00464 no_fading=false;
00465 fadingout_time=current_track.ms_after();
00466 }
00467
00468 static void play_new_music()
00469 {
00470 music_start_time = 0;
00471 want_new_music = true;
00472
00473 if(!preferences::music_on() || !mix_ok || !current_track.valid()) {
00474 return;
00475 }
00476
00477 const std::string& filename = current_track.file_path();
00478
00479 std::map<std::string,Mix_Music*>::const_iterator itor = music_cache.find(filename);
00480 if(itor == music_cache.end()) {
00481 LOG_AUDIO << "attempting to insert track '" << filename << "' into cache\n";
00482 Mix_Music* const music = Mix_LoadMUS(filename.c_str());
00483 if(music == NULL) {
00484 ERR_AUDIO << "Could not load music file '" << filename << "': "
00485 << Mix_GetError() << "\n";
00486 return;
00487 }
00488 itor = music_cache.insert(std::pair<std::string,Mix_Music*>(filename,music)).first;
00489 last_track=current_track;
00490 }
00491
00492 LOG_AUDIO << "Playing track '" << filename << "'\n";
00493 int fading_time=current_track.ms_before();
00494 if(no_fading)
00495 {
00496 fading_time=0;
00497 }
00498
00499 const int res = Mix_FadeInMusic(itor->second, 1, fading_time);
00500 if(res < 0)
00501 {
00502 ERR_AUDIO << "Could not play music: " << Mix_GetError() << " " << filename <<" \n";
00503 }
00504
00505 want_new_music=false;
00506 }
00507
00508 void play_music_repeatedly(const std::string &id)
00509 {
00510
00511 if (id.empty())
00512 return;
00513
00514 current_track_list.clear();
00515 current_track_list.push_back(music_track(id));
00516
00517
00518 if (current_track != id) {
00519 current_track = music_track(id);
00520 play_music();
00521 }
00522 }
00523
00524 void play_music_config(const config &music_node)
00525 {
00526 music_track track( music_node );
00527
00528
00529 if (track.play_once()) {
00530 current_track = track;
00531 play_music();
00532 return;
00533 }
00534
00535
00536 if (!track.append()) {
00537 current_track_list.clear();
00538 }
00539
00540 if(track.valid()) {
00541
00542
00543
00544 std::vector<music_track>::const_iterator itor = current_track_list.begin();
00545 while(itor != current_track_list.end()) {
00546 if(track == *itor) break;
00547 ++itor;
00548 }
00549
00550 if(itor == current_track_list.end()) {
00551 current_track_list.push_back(track);
00552 } else {
00553 ERR_AUDIO << "tried to add duplicate track '" << track.file_path() << "'\n";
00554 }
00555 }
00556 else if(track.id().empty() == false) {
00557 ERR_AUDIO << "cannot open track '" << track.id() << "'; disabled in this playlist.\n";
00558 }
00559
00560
00561 if (track.immediate()) {
00562 current_track = track;
00563 play_music();
00564 }
00565 }
00566
00567 void music_thinker::process(events::pump_info &info) {
00568 if(preferences::music_on()) {
00569 if(!music_start_time && !current_track_list.empty() && !Mix_PlayingMusic()) {
00570
00571 current_track = choose_track();
00572 music_start_time = info.ticks();
00573 no_fading=true;
00574 fadingout_time=0;
00575 }
00576
00577 if(music_start_time && info.ticks(&music_refresh, music_refresh_rate) >= music_start_time - fadingout_time) {
00578 want_new_music=true;
00579 }
00580
00581 if(want_new_music) {
00582 if(Mix_PlayingMusic()) {
00583 Mix_FadeOutMusic(fadingout_time);
00584 }
00585 play_new_music();
00586 }
00587 }
00588 }
00589
00590 void commit_music_changes()
00591 {
00592 played_before.clear();
00593
00594
00595 if (current_track.play_once())
00596 return;
00597
00598
00599 foreach (const music_track &m, current_track_list) {
00600 if (current_track == m)
00601 return;
00602 }
00603
00604
00605 if (current_track_list.empty())
00606 return;
00607
00608
00609 current_track = choose_track();
00610 play_music();
00611 }
00612
00613 void write_music_play_list(config& snapshot)
00614 {
00615
00616 bool append = false;
00617 foreach (music_track &m, current_track_list) {
00618 m.write(snapshot, append);
00619 append = true;
00620 }
00621 }
00622
00623 void reposition_sound(int id, unsigned int distance)
00624 {
00625 audio_lock lock;
00626 for (unsigned ch = 0; ch < channel_ids.size(); ++ch)
00627 {
00628 if (channel_ids[ch] != id) continue;
00629 if (distance >= DISTANCE_SILENT) {
00630 Mix_FadeOutChannel(ch, 100);
00631 } else {
00632 Mix_SetDistance(ch, distance);
00633 }
00634 }
00635 }
00636
00637 bool is_sound_playing(int id)
00638 {
00639 audio_lock lock;
00640 return std::find(channel_ids.begin(), channel_ids.end(), id) != channel_ids.end();
00641 }
00642
00643 void stop_sound(int id)
00644 {
00645 reposition_sound(id, DISTANCE_SILENT);
00646 }
00647
00648 void play_sound_positioned(const std::string &files, int id, int repeats, unsigned int distance)
00649 {
00650 if(preferences::sound_on()) {
00651 play_sound_internal(files, SOUND_SOURCES, repeats, distance, id);
00652 }
00653 }
00654
00655 struct chunk_load_exception { };
00656
00657 static Mix_Chunk* load_chunk(const std::string& file, channel_group group)
00658 {
00659 sound_cache_iterator it_bgn, it_end;
00660 sound_cache_iterator it;
00661
00662 sound_cache_chunk temp_chunk(file);
00663 it_bgn = sound_cache.begin(), it_end = sound_cache.end();
00664 it = std::find(it_bgn, it_end, temp_chunk);
00665
00666 if (it != it_end) {
00667 if(it->group != group) {
00668
00669 it->group = NULL_CHANNEL;
00670 }
00671
00672
00673 sound_cache.splice(it_bgn, sound_cache, it);
00674 } else {
00675
00676 bool cache_full = (sound_cache.size() == max_cached_chunks);
00677 while( cache_full && it != it_bgn ) {
00678
00679 std::vector<Mix_Chunk*>::iterator ch_end = channel_chunks.end();
00680 if(std::find(channel_chunks.begin(), ch_end, (--it)->get_data()) == ch_end) {
00681 sound_cache.erase(it);
00682 cache_full = false;
00683 }
00684 }
00685 if(cache_full) {
00686 LOG_AUDIO << "Maximum sound cache size reached and all are busy, skipping.\n";
00687 throw chunk_load_exception();
00688 }
00689 temp_chunk.group = group;
00690 std::string const &filename = get_binary_file_location("sounds", file);
00691
00692 if (!filename.empty()) {
00693 temp_chunk.set_data(Mix_LoadWAV(filename.c_str()));
00694 } else {
00695 ERR_AUDIO << "Could not load sound file '" << file << "'.\n";
00696 throw chunk_load_exception();
00697 }
00698
00699 if (temp_chunk.get_data() == NULL) {
00700 ERR_AUDIO << "Could not load sound file '" << filename << "': "
00701 << Mix_GetError() << "\n";
00702 throw chunk_load_exception();
00703 }
00704
00705 sound_cache.push_front(temp_chunk);
00706 }
00707
00708 return sound_cache.begin()->get_data();
00709 }
00710
00711 void play_sound_internal(const std::string& files, channel_group group, unsigned int repeats,
00712 unsigned int distance, int id, int loop_ticks, int fadein_ticks)
00713 {
00714 if(files.empty() || distance >= DISTANCE_SILENT || !mix_ok) {
00715 return;
00716 }
00717
00718 audio_lock lock;
00719
00720
00721 int channel = Mix_GroupAvailable(group);
00722 if(channel == -1) {
00723 LOG_AUDIO << "All channels dedicated to sound group(" << group << ") are busy, skipping.\n";
00724 return;
00725 }
00726
00727 Mix_Chunk *chunk;
00728 std::string file = pick_one(files);
00729
00730 try {
00731 chunk = load_chunk(file, group);
00732 assert(chunk);
00733 } catch(const chunk_load_exception&) {
00734 return;
00735 }
00736
00737
00738
00739
00740
00741 if(group != SOUND_UI) {
00742 Mix_SetDistance(channel, distance);
00743 }
00744
00745 int res;
00746 if(loop_ticks > 0) {
00747 if(fadein_ticks > 0) {
00748 res = Mix_FadeInChannelTimed(channel, chunk, -1, fadein_ticks, loop_ticks);
00749 } else {
00750 res = Mix_PlayChannel(channel, chunk, -1);
00751 }
00752
00753 if(res >= 0) {
00754 Mix_ExpireChannel(channel, loop_ticks);
00755 }
00756 } else {
00757 if(fadein_ticks > 0) {
00758 res = Mix_FadeInChannel(channel, chunk, repeats, fadein_ticks);
00759 } else {
00760 res = Mix_PlayChannel(channel, chunk, repeats);
00761 }
00762 }
00763
00764 if(res < 0) {
00765 ERR_AUDIO << "error playing sound effect: " << Mix_GetError() << "\n";
00766
00767 return;
00768 }
00769
00770 channel_ids[channel] = id;
00771
00772
00773 channel_chunks[res] = chunk;
00774 }
00775
00776 void play_sound(const std::string& files, channel_group group, unsigned int repeats)
00777 {
00778 if(preferences::sound_on()) {
00779 play_sound_internal(files, group, repeats);
00780 }
00781 }
00782
00783
00784 void play_bell(const std::string& files)
00785 {
00786 if (preferences::turn_bell()) {
00787 play_sound_internal(files, SOUND_BELL);
00788 }
00789 }
00790
00791
00792 void play_timer(const std::string& files, int loop_ticks, int fadein_ticks)
00793 {
00794 if(preferences::sound_on()) {
00795 play_sound_internal(files, SOUND_TIMER, 0, 0, -1, loop_ticks, fadein_ticks);
00796 }
00797 }
00798
00799
00800 void play_UI_sound(const std::string& files)
00801 {
00802 if(preferences::UI_sound_on()) {
00803 play_sound_internal(files, SOUND_UI);
00804 }
00805 }
00806
00807 void set_music_volume(int vol)
00808 {
00809 if(mix_ok && vol >= 0) {
00810 if(vol > MIX_MAX_VOLUME)
00811 vol = MIX_MAX_VOLUME;
00812
00813 Mix_VolumeMusic(vol);
00814 }
00815 }
00816
00817 void set_sound_volume(int vol)
00818 {
00819 if(mix_ok && vol >= 0) {
00820 if(vol > MIX_MAX_VOLUME)
00821 vol = MIX_MAX_VOLUME;
00822
00823
00824 for (unsigned i = 0; i < n_of_channels; ++i){
00825 if(i != UI_sound_channel && i != bell_channel && i != timer_channel) {
00826 Mix_Volume(i, vol);
00827 }
00828 }
00829 }
00830 }
00831
00832
00833
00834
00835 void set_bell_volume(int vol)
00836 {
00837 if(mix_ok && vol >= 0) {
00838 if(vol > MIX_MAX_VOLUME)
00839 vol = MIX_MAX_VOLUME;
00840
00841 Mix_Volume(bell_channel, vol);
00842 Mix_Volume(timer_channel, vol);
00843 }
00844 }
00845
00846 void set_UI_volume(int vol)
00847 {
00848 if(mix_ok && vol >= 0) {
00849 if(vol > MIX_MAX_VOLUME)
00850 vol = MIX_MAX_VOLUME;
00851
00852 Mix_Volume(UI_sound_channel, vol);
00853 }
00854 }
00855
00856 }