2 * Copyright (c) 2007-2013, Czirkos Zoltan http://code.google.com/p/gdash/
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 /* the replay saver thing only works in the sdl version */
22 #include <SDL/SDL_mixer.h>
23 #include <glib/gi18n.h>
25 #include "framework/replaysaveractivity.hpp"
26 #include "framework/commands.hpp"
27 #include "sdl/IMG_savepng.h"
28 #include "cave/gamecontrol.hpp"
29 #include "cave/titleanimation.hpp"
30 #include "cave/caveset.hpp"
31 #include "misc/logger.hpp"
32 #include "misc/printf.hpp"
33 #include "settings.hpp"
34 #include "sound/sound.hpp"
37 void ReplaySaverActivity::SDLInmemoryScreen::set_title(char const *) {
41 void ReplaySaverActivity::SDLInmemoryScreen::configure_size() {
43 SDL_FreeSurface(surface
);
44 surface
= SDL_CreateRGBSurface(0, w
, h
, 32, 0, 0, 0, 0);
48 ReplaySaverActivity::SDLInmemoryScreen::~SDLInmemoryScreen() {
49 SDL_FreeSurface(surface
);
53 void ReplaySaverActivity::SDLInmemoryScreen::save(char const *filename
) {
54 IMG_SavePNG(filename
, surface
, 2); // 2 = not too much compression, but a bit faster than the default
58 ReplaySaverActivity::ReplaySaverActivity(App
*app
, CaveStored
*cave
, CaveReplay
*replay
, std::string
const &filename_prefix
)
61 filename_prefix(filename_prefix
),
62 game(GameControl::new_replay(app
->caveset
, cave
, replay
)),
66 cellrenderer(pf
, gd_theme
),
67 gamerenderer(pm
, cellrenderer
, fm
, *game
) {
68 int cell_size
= cellrenderer
.get_cell_size();
69 pm
.set_size(cell_size
*GAME_RENDERER_SCREEN_SIZE_X
, cell_size
*GAME_RENDERER_SCREEN_SIZE_Y
);
70 gamerenderer
.set_show_replay_sign(false);
71 std::string wav_filename
= filename_prefix
+ ".wav";
72 wavfile
= fopen(wav_filename
.c_str(), "wb");
74 gd_critical(CPrintf("Cannot open %s for sound output") % wav_filename
);
75 app
->enqueue_command(new PopActivityCommand(app
));
78 fseek(wavfile
, 44, SEEK_SET
); /* 44bytes offset: start of data in a wav file */
84 void ReplaySaverActivity::shown_event() {
85 std::vector
<Pixmap
*> animation
= get_title_animation_pixmap(app
->caveset
->title_screen
, app
->caveset
->title_screen_scroll
, true, pf
);
86 pm
.blit(*animation
[0], 0, 0);
90 /* enable own timer and sound saver */
95 ReplaySaverActivity::~ReplaySaverActivity() {
96 Mix_SetPostMix(NULL
, NULL
); /* remove wav file saver */
97 uninstall_own_mixer();
100 /* write wav header, as now we now its final size. */
101 fseek(wavfile
, 0, SEEK_SET
);
106 i
+=fwrite("RIFF", 1, 4, wavfile
); /* "RIFF" */
107 out32
=GUINT32_TO_LE(wavlen
+36);
108 i
+=fwrite(&out32
, 1, 4, wavfile
); /* 4 + 8+subchunk1size + 8+subchunk2size */
109 i
+=fwrite("WAVE", 1, 4, wavfile
); /* "WAVE" */
111 i
+=fwrite("fmt ", 1, 4, wavfile
); /* "fmt " */
112 out32
=GUINT32_TO_LE(16);
113 i
+=fwrite(&out32
, 1, 4, wavfile
); /* fmt chunk size=16 bytes */
114 out16
=GUINT16_TO_LE(1);
115 i
+=fwrite(&out16
, 1, 2, wavfile
); /* 1=pcm */
116 out16
=GUINT16_TO_LE(channels
);
117 i
+=fwrite(&out16
, 1, 2, wavfile
);
118 out32
=GUINT32_TO_LE(frequency
);
119 i
+=fwrite(&out32
, 1, 4, wavfile
);
120 out32
=GUINT32_TO_LE(frequency
*bits
/8*channels
);
121 i
+=fwrite(&out32
, 1, 4, wavfile
); /* byterate */
122 out16
=GUINT16_TO_LE(bits
/8*channels
);
123 i
+=fwrite(&out16
, 1, 2, wavfile
); /* blockalign */
124 out16
=GUINT16_TO_LE(bits
);
125 i
+=fwrite(&out16
, 1, 2, wavfile
); /* bitspersample */
127 i
+=fwrite("data", 1, 4, wavfile
); /* "data" */
128 out32
=GUINT32_TO_LE(wavlen
);
129 i
+=fwrite(&out32
, 1, 4, wavfile
); /* actual data length */
133 gd_critical("Could not write wav header to file!");
135 std::string message
= SPrintf(_("Saved %d video frames and %dMiB of audio data to %s_*.png and %s.wav.")) % (frame
+1) % (wavlen
/1048576) % filename_prefix
% filename_prefix
;
136 app
->show_message(_("Replay Saved"), message
);
139 gd_show_name_of_game
=saved_gd_show_name_of_game
;
140 gd_sound_set_music_volume();
141 gd_sound_set_chunk_volumes();
142 gd_music_play_random();
147 void ReplaySaverActivity::install_own_mixer() {
150 /* we setup mixing and other parameters for our own needs. */
151 saved_gd_sdl_sound
=gd_sound_enabled
;
152 saved_gd_sdl_44khz_mixing
=gd_sound_44khz_mixing
;
153 saved_gd_sdl_16bit_mixing
=gd_sound_16bit_mixing
;
154 saved_gd_sound_stereo
=gd_sound_stereo
;
155 const char *driver
=g_getenv("SDL_AUDIODRIVER");
159 /* and after tweaking settings to our own need, we can init sdl. */
160 gd_sound_enabled
=true;
161 gd_sound_44khz_mixing
=true;
162 gd_sound_16bit_mixing
=true;
163 gd_sound_stereo
=true;
164 // select the dummy driver, as it accepts any format, and we have control
165 g_setenv("SDL_AUDIODRIVER", "dummy", TRUE
);
166 gd_sound_init(44100/25); /* so the buffer will be 1/25th of a second. and we get our favourite 25hz interrupt from the mixer. */
168 /* query audio format from sdl */
170 Mix_QuerySpec(&frequency
, &format
, &channels
);
171 if (frequency
!=44100) /* something must be really going wrong. */
172 gd_critical("Cannot initialize mixer to 44100Hz mixing. The replay saver will not work correctly!");
194 g_assert_not_reached();
196 Mix_SetPostMix(mixfunc
, this);
200 void ReplaySaverActivity::uninstall_own_mixer() {
204 gd_sound_enabled
=saved_gd_sdl_sound
;
205 gd_sound_44khz_mixing
=saved_gd_sdl_44khz_mixing
;
206 gd_sound_16bit_mixing
=saved_gd_sdl_16bit_mixing
;
207 gd_sound_stereo
=saved_gd_sound_stereo
;
209 if (saved_driver
!="")
210 g_setenv("SDL_AUDIODRIVER", saved_driver
.c_str(), TRUE
);
212 g_unsetenv("SDL_AUDIODRIVER");
218 /* this function saves the wav file, and also does the timing! */
219 void ReplaySaverActivity::mixfunc(void *udata
, Uint8
*stream
, int len
) {
220 ReplaySaverActivity
*rs
=static_cast<ReplaySaverActivity
*>(udata
);
224 if (fwrite(stream
, 1, len
, rs
->wavfile
)!=size_t(len
))
225 gd_critical("Cannot write to wav file!");
227 // push a user event -> to do the timing
229 ev
.type
=SDL_USEREVENT
+1;
236 void ReplaySaverActivity::redraw_event() {
239 app
->title_line(_("Saving Replay"));
240 app
->status_line(_("Please wait"));
242 // show it to the user.
243 // it is not in displayformat, but a small image - not that slow to draw.
244 // center coordinates
245 int x
=(app
->screen
->get_width()-pm
.get_width())/2;
246 int y
=(app
->screen
->get_height()-pm
.get_height())/2;
253 SDL_BlitSurface(pm
.get_surface(), 0, static_cast<SDLScreen
*>(app
->screen
)->get_surface(), &destr
);
259 void ReplaySaverActivity::timer2_event() {
260 /* iterate and see what happened */
261 /* no movement, no fire, no suicide, no restart, no pause, no fast movement.
262 * the gamerenderer takes the moves from the replay. if we would add a move or a fire
263 * press, the gamerenderer would switch to "continue replay" mode. */
266 GameRenderer::State state
= gamerenderer
.main_int(40, MV_STILL
, false, suicide
, restart
, false, false, false);
268 case GameRenderer::CaveLoaded
:
269 case GameRenderer::Iterated
:
270 case GameRenderer::Nothing
:
273 case GameRenderer::Stop
: /* game stopped, this could be a replay or a snapshot */
274 case GameRenderer::GameOver
: /* game over should not happen for a replay, but no problem */
275 app
->enqueue_command(new PopActivityCommand(app
));
279 /* before incrementing frame number, check if to save the frame to disk. */
280 char *filename
=g_strdup_printf("%s_%08d.png", filename_prefix
.c_str(), frame
);
288 #endif /* IFDEF HAVE_SDL */