gosu 0.12.1 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/Gosu/Audio.hpp +23 -25
  3. data/Gosu/Graphics.hpp +16 -12
  4. data/Gosu/Image.hpp +3 -0
  5. data/Gosu/Version.hpp +2 -2
  6. data/lib/gosu.rb +2 -2
  7. data/lib/gosu/compat.rb +1 -1
  8. data/lib/gosu/patches.rb +5 -0
  9. data/lib/gosu/swig_patches.rb +1 -1
  10. data/rdoc/gosu.rb +10 -10
  11. data/src/Audio.cpp +93 -228
  12. data/src/AudioImpl.cpp +94 -0
  13. data/src/AudioImpl.hpp +33 -0
  14. data/src/AudioToolboxFile.hpp +14 -18
  15. data/src/Bitmap.cpp +36 -30
  16. data/src/BitmapIO.cpp +14 -23
  17. data/src/BlockAllocator.cpp +7 -10
  18. data/src/BlockAllocator.hpp +2 -4
  19. data/src/Channel.cpp +89 -0
  20. data/src/Color.cpp +4 -9
  21. data/src/DirectoriesApple.cpp +13 -13
  22. data/src/DirectoriesUnix.cpp +8 -7
  23. data/src/DirectoriesWin.cpp +12 -11
  24. data/src/EmptyImageData.hpp +54 -0
  25. data/src/FileUnix.cpp +12 -9
  26. data/src/FileWin.cpp +8 -7
  27. data/src/Font.cpp +12 -13
  28. data/src/FormattedString.cpp +237 -0
  29. data/src/FormattedString.hpp +14 -265
  30. data/src/GosuViewController.cpp +2 -5
  31. data/src/Graphics.cpp +38 -39
  32. data/src/IO.cpp +11 -10
  33. data/src/Image.cpp +16 -9
  34. data/src/Input.cpp +16 -15
  35. data/src/InputUIKit.cpp +8 -7
  36. data/src/Macro.cpp +11 -11
  37. data/src/Math.cpp +9 -8
  38. data/src/RubyGosu.cxx +129 -99
  39. data/src/TextApple.cpp +19 -13
  40. data/src/TextInput.cpp +23 -22
  41. data/src/TextWin.cpp +17 -19
  42. data/src/Texture.cpp +15 -10
  43. data/src/Transform.cpp +13 -17
  44. data/src/Utility.cpp +3 -2
  45. data/src/UtilityApple.cpp +10 -11
  46. data/src/UtilityWin.cpp +2 -1
  47. data/src/Version.cpp +5 -4
  48. data/src/WinMain.cpp +3 -3
  49. data/src/WinUtility.cpp +7 -6
  50. data/src/Window.cpp +11 -10
  51. data/src/WindowUIKit.cpp +9 -8
  52. data/src/stb_image.h +782 -480
  53. data/src/stb_image_write.h +425 -15
  54. data/src/stb_vorbis.c +82 -32
  55. metadata +8 -4
  56. data/src/ALChannelManagement.hpp +0 -119
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0e780ad64700f539c7956253b0900d69ab828562
4
- data.tar.gz: bf9eea3a88a216860bac22d6cb2b16dddbe363b1
3
+ metadata.gz: 41378387cae1afdcfbfeab14e6adea7ad61aa1e6
4
+ data.tar.gz: b83153c5f9208a2a201154f012c0135f25238b3e
5
5
  SHA512:
6
- metadata.gz: cae8120cd63eac622abf7a59a5c8e2735dc8d35f5c20a91f4189800d577736ff5eca92e22f1b295165f833f1dc546ada16fb3979a5c5624853242841baee4d8b
7
- data.tar.gz: fee3c74049220d40390106f33f444d739b4d614bcdc253225aa6fc2445b936c0de5650c51e9bc34e3ff2c242e0e6d5a5ba782d8558eafbccfcb9f6a7b40e9b35
6
+ metadata.gz: fccec2f1c9755fe8133d02a4b3f2e31789681938a59a41893a6d247ff29a3fdb68d1c5848390c63dfdb490259b552cd05c2d1833cfdd11533d28d1be07817be2
7
+ data.tar.gz: 6542882a5ef52f9360444efe0513b3c31b51912c5c21a18be5cc76e6b1bdf223e5981755afe860cdd45383b0db1fd52cd60028f690450ed33ed25d127dfc9049
@@ -11,41 +11,36 @@
11
11
 
12
12
  namespace Gosu
13
13
  {
14
- //! An instance of a Sample playing. Can be used to stop sounds dynamically,
15
- //! or to check if they are finished.
16
- //! It is recommended that you throw away sample instances if possible,
17
- //! as they could accidentally refer to other sounds being played after
18
- //! a very long time has passed.
19
- class SampleInstance
14
+ //! Sample::play returns a Channel that represents the sound currently being played.
15
+ //! This object can be used to stop sounds dynamically, or to check whether they have finished.
16
+ class Channel
20
17
  {
21
- int handle, extra;
22
- bool alive() const;
18
+ mutable int channel, token;
23
19
 
24
20
  public:
25
- //! Called internally by Sample, do not use.
26
- SampleInstance(int handle, int extra);
27
-
21
+ //! For internal use only.
22
+ Channel(int channel, int token);
23
+
24
+ int current_channel() const;
25
+
28
26
  bool playing() const;
29
27
  bool paused() const;
30
- //! Pauses this instance to be resumed afterwards. It will still keep a channel filled while
31
- //! paused.
28
+ //! Pauses this instance to be resumed afterwards.
29
+ //! It will still occupy an audio channel while paused.
32
30
  void pause();
33
31
  void resume();
34
32
  //! Stops this instance of a sound being played.
35
33
  //! Calling this twice, or too late, does not do any harm.
36
34
  void stop();
37
35
 
38
- //! \param volume Can be anything from 0.0 (silence) to 1.0 (full
39
- //! volume).
40
- void change_volume(double volume);
36
+ //! \param volume Can be anything from 0.0 (silence) to 1.0 (full volume).
37
+ void set_volume(double volume);
41
38
  //! \param pan Can be anything from -1.0 (left) to 1.0 (right).
42
- void change_pan(double pan);
43
- //! \param speed Playback speed is only limited by FMOD's
44
- //! capabilities and can accept very high or low values. Use 1.0 for
45
- //! normal playback speed.
46
- void change_speed(double speed);
39
+ void set_pan(double pan);
40
+ //! \param speed Use 1.0 for normal playback speed.
41
+ void set_speed(double speed);
47
42
  };
48
-
43
+
49
44
  //! A sample is a short sound that is completely loaded in memory, can be
50
45
  //! played multiple times at once and offers very flexible playback
51
46
  //! parameters. Use samples for everything that's not music.
@@ -55,6 +50,9 @@ namespace Gosu
55
50
  std::shared_ptr<SampleData> data;
56
51
 
57
52
  public:
53
+ //! Constructs an empty sample that acts as if the song had a length of 0.
54
+ Sample();
55
+
58
56
  //! Constructs a sample that can be played on the specified audio
59
57
  //! system and loads the sample from a file.
60
58
  explicit Sample(const std::string& filename);
@@ -69,7 +67,7 @@ namespace Gosu
69
67
  //! \param speed Playback speed is only limited by the underlying audio library,
70
68
  //! and can accept very high or low values. Use 1.0 for
71
69
  //! normal playback speed.
72
- SampleInstance play(double volume = 1, double speed = 1, bool looping = false) const;
70
+ Channel play(double volume = 1, double speed = 1, bool looping = false) const;
73
71
 
74
72
  //! Plays the sample with panning. Even if pan is 0.0, the sample will
75
73
  //! not be as loud as if it were played by calling play() due to the
@@ -80,7 +78,7 @@ namespace Gosu
80
78
  //! \param speed Playback speed is only limited by by the underlying audio library,
81
79
  //! and can accept very high
82
80
  //! or low values. Use 1.0 for normal playback speed.
83
- SampleInstance play_pan(double pan, double volume = 1, double speed = 1,
81
+ Channel play_pan(double pan, double volume = 1, double speed = 1,
84
82
  bool looping = false) const;
85
83
  };
86
84
 
@@ -133,7 +131,7 @@ namespace Gosu
133
131
  double volume() const;
134
132
  //! \param volume Can be anything from 0.0 (silence) to 1.0 (full
135
133
  //! volume).
136
- void change_volume(double volume);
134
+ void set_volume(double volume);
137
135
 
138
136
  //! Called every tick by Window for management purposes.
139
137
  static void update();
@@ -32,7 +32,7 @@ namespace Gosu
32
32
  ~Graphics();
33
33
 
34
34
  void set_resolution(unsigned logical_width, unsigned logical_height,
35
- double horizontal_black_bar_width = 0, double vertical_black_bar_height = 0);
35
+ double black_bar_width = 0, double black_bar_height = 0);
36
36
 
37
37
  unsigned width() const;
38
38
  unsigned height() const;
@@ -75,19 +75,22 @@ namespace Gosu
75
75
  //! or end point. Please only use this for debugging purposes. Otherwise, use a quad or
76
76
  //! image to simulate lines, or contribute a better draw_line to Gosu.
77
77
  static void draw_line(double x1, double y1, Color c1,
78
- double x2, double y2, Color c2,
79
- ZPos z, AlphaMode mode = AM_DEFAULT);
78
+ double x2, double y2, Color c2,
79
+ ZPos z, AlphaMode mode = AM_DEFAULT);
80
80
 
81
81
  static void draw_triangle(double x1, double y1, Color c1,
82
- double x2, double y2, Color c2,
83
- double x3, double y3, Color c3,
84
- ZPos z, AlphaMode mode = AM_DEFAULT);
82
+ double x2, double y2, Color c2,
83
+ double x3, double y3, Color c3,
84
+ ZPos z, AlphaMode mode = AM_DEFAULT);
85
85
 
86
86
  static void draw_quad(double x1, double y1, Color c1,
87
- double x2, double y2, Color c2,
88
- double x3, double y3, Color c3,
89
- double x4, double y4, Color c4,
90
- ZPos z, AlphaMode mode = AM_DEFAULT);
87
+ double x2, double y2, Color c2,
88
+ double x3, double y3, Color c3,
89
+ double x4, double y4, Color c4,
90
+ ZPos z, AlphaMode mode = AM_DEFAULT);
91
+
92
+ static void draw_rect(double x, double y, double width, double height,
93
+ Color c, ZPos z, AlphaMode mode = AM_DEFAULT);
91
94
 
92
95
  //! For internal use only.
93
96
  void set_physical_resolution(unsigned physical_width, unsigned physical_height);
@@ -97,7 +100,8 @@ namespace Gosu
97
100
 
98
101
  //! Turns a portion of a bitmap into something that can be drawn on a Graphics object.
99
102
  static std::unique_ptr<ImageData> create_image(const Bitmap& src,
100
- unsigned src_x, unsigned src_y, unsigned src_width, unsigned src_height,
101
- unsigned image_flags);
103
+ unsigned src_x, unsigned src_y,
104
+ unsigned src_width, unsigned src_height,
105
+ unsigned image_flags);
102
106
  };
103
107
  }
@@ -17,6 +17,9 @@ namespace Gosu
17
17
  std::shared_ptr<ImageData> data_;
18
18
 
19
19
  public:
20
+ //! Creates an empty image. It will have a width and height of 0, and not contain anything.
21
+ Image();
22
+
20
23
  //! Loads an image from a given filename.
21
24
  //!
22
25
  //! A color key of #ff00ff is automatically applied to BMP image files.
@@ -3,8 +3,8 @@
3
3
  #include <string>
4
4
 
5
5
  #define GOSU_MAJOR_VERSION 0
6
- #define GOSU_MINOR_VERSION 12
7
- #define GOSU_POINT_VERSION 1
6
+ #define GOSU_MINOR_VERSION 13
7
+ #define GOSU_POINT_VERSION 0
8
8
 
9
9
  namespace Gosu
10
10
  {
@@ -7,14 +7,14 @@ if RUBY_PLATFORM =~ /mswin$|mingw32|mingw64|win32\-|\-win32/
7
7
 
8
8
  begin
9
9
  # Make DLLs available as shown here:
10
- # https://github.com/oneclick/rubyinstaller2/wiki/For-gem-developers
10
+ # https://github.com/oneclick/rubyinstaller2/wiki/For-gem-developers
11
11
  require 'ruby_installer'
12
12
  RubyInstaller::Runtime.add_dll_directory(binary_path)
13
13
  rescue LoadError
14
14
  # Add this gem to the PATH on Windows so that bundled DLLs can be found.
15
15
  # When running through Ocra on Windows, we need to be careful to preserve the ENV["PATH"]
16
16
  # encoding (see #385).
17
- path_encoding = ENV["PATH"].encoding
17
+ path_encoding = ENV["PATH"].encoding
18
18
  ENV["PATH"] = "#{binary_path.encode(path_encoding)};#{ENV["PATH"]}"
19
19
  end
20
20
 
@@ -143,7 +143,7 @@ class Gosu::Window
143
143
  end
144
144
 
145
145
  # Instance methods that have been turned into module methods.
146
- %w(draw_line draw_triangle draw_quad
146
+ %w(draw_line draw_triangle draw_quad draw_rect
147
147
  flush gl clip_to record
148
148
  transform translate rotate scale
149
149
  button_id_to_char char_to_button_id button_down?).each do |method|
@@ -45,6 +45,11 @@ module Gosu
45
45
  end
46
46
  end
47
47
 
48
+ module Gosu
49
+ # Backwards compatibility: Channel was called SampleInstance before Gosu 0.13.0.
50
+ SampleInstance = Channel
51
+ end
52
+
48
53
  class Gosu::Window
49
54
  # Call Thread.pass every tick, which may or may not be necessary for friendly co-existence with
50
55
  # Ruby's Thread class.
@@ -30,7 +30,7 @@ class Gosu::Window
30
30
  rescue Exception => e
31
31
  # Exit the message loop naturally, then re-throw during the next tick.
32
32
  @_exception = e
33
- close
33
+ close!
34
34
  false
35
35
  end
36
36
  end
@@ -506,9 +506,9 @@ module Gosu
506
506
  ##
507
507
  # Plays the sample without panning.
508
508
  #
509
- # @return [SampleInstance]
510
- # @param volume [Float] see {SampleInstance#volume=}
511
- # @param speed [Float] see {SampleInstance#speed=}
509
+ # @return [Channel]
510
+ # @param volume [Float] see {Channel#volume=}
511
+ # @param speed [Float] see {Channel#speed=}
512
512
  # @param looping [true, false] whether the sample should play in a loop. If you pass true, be sure to store the return value of this method so that you can later stop the looping sound.
513
513
  #
514
514
  # @see #play_pan
@@ -517,10 +517,10 @@ module Gosu
517
517
  ##
518
518
  # Plays the sample with panning.
519
519
  #
520
- # @return [SampleInstance]
521
- # @param pan [Float] see {SampleInstance#pan=}
522
- # @param volume [Float] see {SampleInstance#volume=}
523
- # @param speed [Float] see {SampleInstance#speed=}
520
+ # @return [Channel]
521
+ # @param pan [Float] see {Channel#pan=}
522
+ # @param volume [Float] see {Channel#volume=}
523
+ # @param speed [Float] see {Channel#speed=}
524
524
  # @param looping [true, false] whether the sample should play in a loop. If you pass true, be sure to store the return value of this method so that you can later stop the looping sound.
525
525
  #
526
526
  # @see #play
@@ -528,10 +528,10 @@ module Gosu
528
528
  end
529
529
 
530
530
  ##
531
- # An instance of a {Gosu::Sample} playing. Can be used to stop sounds dynamically, or to check if they are finished.
531
+ # {Sample#play} returns a Channel that represents the sound currently being played.
532
532
  #
533
- # It is recommended to throw away sample instances as soon as possible, as holding onto them for a long time can prevent unneeded samples being properly disposed.
534
- class SampleInstance
533
+ # This object can be used to stop sounds dynamically, or to check whether they have finished.
534
+ class Channel
535
535
  ##
536
536
  # Sets the playback volume, in the range [0.0; 1.0], where 0 is completely silent and 1 is full volume. Values outside of this range will be clamped to [0.0; 1.0].
537
537
  # @param [Float]
@@ -1,4 +1,4 @@
1
- #include "ALChannelManagement.hpp"
1
+ #include "AudioImpl.hpp"
2
2
  #include "OggFile.hpp"
3
3
  #include <Gosu/Audio.hpp>
4
4
  #include <Gosu/Math.hpp>
@@ -8,19 +8,12 @@
8
8
  #include <cassert>
9
9
  #include <cstdlib>
10
10
  #include <algorithm>
11
- #include <memory>
12
- #include <stdexcept>
13
- #include <vector>
14
11
 
15
12
  #ifdef GOSU_IS_MAC
16
- #include <OpenAL/al.h>
17
- #include <OpenAL/alc.h>
18
13
  #import <Foundation/Foundation.h>
19
14
  #include "AudioToolboxFile.hpp"
20
15
  #define WAVE_FILE AudioToolboxFile
21
16
  #else
22
- #include <AL/al.h>
23
- #include <AL/alc.h>
24
17
  #include "MPEGFile.hpp"
25
18
  #include "SndFile.hpp"
26
19
  #define WAVE_FILE SndFile
@@ -32,116 +25,23 @@
32
25
 
33
26
  using namespace std;
34
27
 
35
- namespace
28
+ static bool is_ogg_file(Gosu::Reader reader)
36
29
  {
37
- bool is_ogg_file(Gosu::Reader reader)
38
- {
39
- char magic_bytes[4];
40
- reader.read(magic_bytes, 4);
41
- return magic_bytes[0] == 'O' && magic_bytes[1] == 'g' &&
42
- magic_bytes[2] == 'g' && magic_bytes[3] == 'S';
43
- }
44
-
45
- bool is_ogg_file(const string& filename)
46
- {
47
- Gosu::File file(filename);
48
- return is_ogg_file(file.front_reader());
49
- }
50
-
51
- Gosu::Song* cur_song = nullptr;
52
- bool cur_song_looping;
53
- }
54
-
55
- #ifdef GOSU_IS_MAC
56
- #define CONSTRUCTOR_BEGIN \
57
- @autoreleasepool { \
58
- if (!al_channel_management.get()) \
59
- al_channel_management.reset(new ALChannelManagement)
60
- #define CONSTRUCTOR_END \
61
- }
62
- #else
63
- #define CONSTRUCTOR_BEGIN \
64
- if (!al_channel_management.get()) \
65
- al_channel_management.reset(new ALChannelManagement)
66
- #define CONSTRUCTOR_END
67
- #endif
68
-
69
- Gosu::SampleInstance::SampleInstance(int handle, int extra)
70
- : handle(handle), extra(extra)
71
- {
72
- }
73
-
74
- bool Gosu::SampleInstance::playing() const
75
- {
76
- ALuint source = al_channel_management->source_if_still_playing(handle, extra);
77
- if (source == ALChannelManagement::NO_SOURCE) return false;
30
+ if (reader.resource().size() < 4) return false;
78
31
 
79
- ALint state;
80
- alGetSourcei(source, AL_SOURCE_STATE, &state);
81
- return state == AL_PLAYING;
32
+ char bytes[4];
33
+ reader.read(bytes, 4);
34
+ return bytes[0] == 'O' && bytes[1] == 'g' && bytes[2] == 'g' && bytes[3] == 'S';
82
35
  }
83
36
 
84
- bool Gosu::SampleInstance::paused() const
37
+ static bool is_ogg_file(const string& filename)
85
38
  {
86
- ALuint source = al_channel_management->source_if_still_playing(handle, extra);
87
- if (source == ALChannelManagement::NO_SOURCE) return false;
88
-
89
- ALint state;
90
- alGetSourcei(source, AL_SOURCE_STATE, &state);
91
- return state == AL_PAUSED;
39
+ Gosu::File file(filename);
40
+ return is_ogg_file(file.front_reader());
92
41
  }
93
42
 
94
- void Gosu::SampleInstance::pause()
95
- {
96
- ALuint source = al_channel_management->source_if_still_playing(handle, extra);
97
- if (source == ALChannelManagement::NO_SOURCE) return;
98
-
99
- alSourcePause(source);
100
- }
101
-
102
- void Gosu::SampleInstance::resume()
103
- {
104
- ALuint source = al_channel_management->source_if_still_playing(handle, extra);
105
- if (source == ALChannelManagement::NO_SOURCE) return;
106
-
107
- ALint state;
108
- alGetSourcei(source, AL_SOURCE_STATE, &state);
109
- if (state == AL_PAUSED) {
110
- alSourcePlay(source);
111
- }
112
- }
113
-
114
- void Gosu::SampleInstance::stop()
115
- {
116
- ALuint source = al_channel_management->source_if_still_playing(handle, extra);
117
- if (source == ALChannelManagement::NO_SOURCE) return;
118
-
119
- alSourceStop(source);
120
- }
121
-
122
- void Gosu::SampleInstance::change_volume(double volume)
123
- {
124
- ALuint source = al_channel_management->source_if_still_playing(handle, extra);
125
- if (source == ALChannelManagement::NO_SOURCE) return;
126
-
127
- alSourcef(source, AL_GAIN, max(volume, 0.0));
128
- }
129
-
130
- void Gosu::SampleInstance::change_pan(double pan)
131
- {
132
- ALuint source = al_channel_management->source_if_still_playing(handle, extra);
133
- if (source == ALChannelManagement::NO_SOURCE) return;
134
-
135
- alSource3f(source, AL_POSITION, pan * 10, 0, 0);
136
- }
137
-
138
- void Gosu::SampleInstance::change_speed(double speed)
139
- {
140
- ALuint source = al_channel_management->source_if_still_playing(handle, extra);
141
- if (source == ALChannelManagement::NO_SOURCE) return;
142
-
143
- alSourcef(source, AL_PITCH, speed);
144
- }
43
+ static Gosu::Song* cur_song = nullptr;
44
+ static bool cur_song_looping;
145
45
 
146
46
  struct Gosu::Sample::SampleData
147
47
  {
@@ -149,6 +49,7 @@ struct Gosu::Sample::SampleData
149
49
 
150
50
  SampleData(AudioFile& audio_file)
151
51
  {
52
+ al_initialize();
152
53
  alGenBuffers(1, &buffer);
153
54
  alBufferData(buffer, audio_file.format(), &audio_file.decoded_data().front(),
154
55
  static_cast<ALsizei>(audio_file.decoded_data().size()),
@@ -159,24 +60,20 @@ struct Gosu::Sample::SampleData
159
60
  {
160
61
  // It's hard to free things in the right order in Ruby/Gosu.
161
62
  // Make sure buffer isn't deleted after the context/device are shut down.
162
- if (!al_channel_management.get()) {
163
- return;
164
- }
63
+ if (!al_initialized()) return;
165
64
 
166
65
  alDeleteBuffers(1, &buffer);
167
66
  }
168
-
169
- private:
170
- SampleData(const SampleData&);
171
- SampleData& operator=(const SampleData&);
172
67
  };
173
68
 
174
- Gosu::Sample::Sample(const string& filename)
69
+ Gosu::Sample::Sample()
175
70
  {
176
- CONSTRUCTOR_BEGIN;
71
+ }
177
72
 
73
+ Gosu::Sample::Sample(const string& filename)
74
+ {
178
75
  if (is_ogg_file(filename)) {
179
- Gosu::File file(filename);
76
+ File file(filename);
180
77
  OggFile ogg_file(file.front_reader());
181
78
  data.reset(new SampleData(ogg_file));
182
79
  }
@@ -184,62 +81,54 @@ Gosu::Sample::Sample(const string& filename)
184
81
  WAVE_FILE audio_file(filename);
185
82
  data.reset(new SampleData(audio_file));
186
83
  }
187
-
188
- CONSTRUCTOR_END;
189
84
  }
190
85
 
191
- Gosu::Sample::Sample(Reader reader)
86
+ Gosu::Sample::Sample(Gosu::Reader reader)
192
87
  {
193
- CONSTRUCTOR_BEGIN;
194
-
195
88
  if (is_ogg_file(reader)) {
196
89
  OggFile ogg_file(reader);
197
90
  data.reset(new SampleData(ogg_file));
91
+ return;
198
92
  }
199
- else {
200
- try {
201
- WAVE_FILE audio_file(reader);
202
- data.reset(new SampleData(audio_file));
203
- }
204
- catch (const runtime_error& ex) {
205
- #ifndef GOSU_IS_MAC
206
- if (string(ex.what()).find("unknown format") != string::npos) {
207
- MPEGFile mpeg_file(reader);
208
- data.reset(new SampleData(mpeg_file));
209
- }
210
- else
211
- #endif
212
- throw ex;
93
+
94
+ try {
95
+ WAVE_FILE audio_file(reader);
96
+ data.reset(new SampleData(audio_file));
97
+ }
98
+ catch (const runtime_error& ex) {
99
+ #ifndef GOSU_IS_MAC
100
+ if (string(ex.what()).find("unknown format") != string::npos) {
101
+ MPEGFile mpeg_file(reader);
102
+ data.reset(new SampleData(mpeg_file));
103
+ return;
213
104
  }
105
+ #endif
106
+ throw ex;
214
107
  }
215
-
216
- CONSTRUCTOR_END;
217
108
  }
218
109
 
219
- Gosu::SampleInstance Gosu::Sample::play(double volume, double speed, bool looping) const
110
+ Gosu::Channel Gosu::Sample::play(double volume, double speed, bool looping) const
220
111
  {
221
112
  return play_pan(0, volume, speed, looping);
222
113
  }
223
114
 
224
- Gosu::SampleInstance Gosu::Sample::play_pan(double pan, double volume, double speed,
225
- bool looping) const
115
+ Gosu::Channel Gosu::Sample::play_pan(double pan, double volume, double speed, bool looping) const
226
116
  {
227
- pair<int, int> channel_and_token = al_channel_management->reserve_channel();
228
- if (channel_and_token.first == ALChannelManagement::NO_FREE_CHANNEL) {
229
- return Gosu::SampleInstance(channel_and_token.first, channel_and_token.second);
230
- }
117
+ if (!data) return Channel(NO_CHANNEL, 0);
118
+
119
+ Channel channel = allocate_channel();
120
+
121
+ // Couldn't allocate a free channel.
122
+ if (channel.current_channel() == NO_CHANNEL) return channel;
231
123
 
232
- ALuint source = al_channel_management->source_if_still_playing(channel_and_token.first,
233
- channel_and_token.second);
234
- assert (source != ALChannelManagement::NO_SOURCE);
124
+ ALuint source = al_source_for_channel(channel.current_channel());
235
125
  alSourcei(source, AL_BUFFER, data->buffer);
236
126
  alSource3f(source, AL_POSITION, pan * 10, 0, 0);
237
127
  alSourcef(source, AL_GAIN, max(volume, 0.0));
238
128
  alSourcef(source, AL_PITCH, speed);
239
129
  alSourcei(source, AL_LOOPING, looping ? AL_TRUE : AL_FALSE);
240
130
  alSourcePlay(source);
241
-
242
- return Gosu::SampleInstance(channel_and_token.first, channel_and_token.second);
131
+ return channel;
243
132
  }
244
133
 
245
134
  class Gosu::Song::BaseData
@@ -267,7 +156,7 @@ public:
267
156
  return volume_;
268
157
  }
269
158
 
270
- void change_volume(double volume)
159
+ void set_volume(double volume)
271
160
  {
272
161
  volume_ = clamp(volume, 0.0, 1.0);
273
162
  apply_volume();
@@ -336,15 +225,7 @@ class Gosu::Song::StreamData : public BaseData
336
225
 
337
226
  void apply_volume() override
338
227
  {
339
- int source = lookup_source();
340
- if (source != ALChannelManagement::NO_SOURCE) {
341
- alSourcef(source, AL_GAIN, max(volume(), 0.0));
342
- }
343
- }
344
-
345
- int lookup_source() const
346
- {
347
- return al_channel_management->source_for_songs();
228
+ alSourcef(al_source_for_songs(), AL_GAIN, max(volume(), 0.0));
348
229
  }
349
230
 
350
231
  bool stream_to_buffer(ALuint buffer)
@@ -367,7 +248,7 @@ public:
367
248
  StreamData(const string& filename)
368
249
  {
369
250
  if (is_ogg_file(filename)) {
370
- Gosu::File source_file(filename);
251
+ File source_file(filename);
371
252
  file.reset(new OggFile(source_file.front_reader()));
372
253
  }
373
254
  else {
@@ -377,14 +258,16 @@ public:
377
258
  catch (const runtime_error& ex) {
378
259
  #ifndef GOSU_IS_MAC
379
260
  if (string(ex.what()).find("unknown format") != string::npos) {
380
- Gosu::File source_file(filename);
261
+ File source_file(filename);
381
262
  file.reset(new MPEGFile(source_file.front_reader()));
382
263
  }
383
264
  else
384
265
  #endif
385
- throw ex;
266
+ throw ex;
386
267
  }
387
268
  }
269
+
270
+ al_initialize();
388
271
  alGenBuffers(2, buffers);
389
272
  }
390
273
 
@@ -404,88 +287,79 @@ public:
404
287
  }
405
288
  else
406
289
  #endif
407
- throw ex;
290
+ throw ex;
408
291
  }
409
292
  }
293
+
294
+ al_initialize();
410
295
  alGenBuffers(2, buffers);
411
296
  }
412
297
 
413
298
  ~StreamData()
414
299
  {
415
- if (al_channel_management.get()) {
416
- alDeleteBuffers(2, buffers);
417
- }
300
+ // It's hard to free things in the right order in Ruby/Gosu.
301
+ // Make sure buffers aren't deleted after the context/device are shut down.
302
+ if (!al_initialized()) return;
303
+
304
+ alDeleteBuffers(2, buffers);
418
305
  }
419
306
 
420
307
  void play(bool looping) override
421
308
  {
422
- int source = lookup_source();
423
- if (source != ALChannelManagement::NO_SOURCE) {
424
- alSource3f(source, AL_POSITION, 0, 0, 0);
425
- alSourcef(source, AL_GAIN, max(volume(), 0.0));
426
- alSourcef(source, AL_PITCH, 1);
427
- alSourcei(source, AL_LOOPING, AL_FALSE); // need to implement this manually...
428
-
429
- stream_to_buffer(buffers[0]);
430
- stream_to_buffer(buffers[1]);
431
-
432
- // TODO: Not good for songs with less than two buffers full of data.
433
-
434
- alSourceQueueBuffers(source, 2, buffers);
435
- alSourcePlay(source);
436
- }
309
+ ALuint source = al_source_for_songs();
310
+
311
+ alSource3f(source, AL_POSITION, 0, 0, 0);
312
+ alSourcef(source, AL_GAIN, max(volume(), 0.0));
313
+ alSourcef(source, AL_PITCH, 1);
314
+ alSourcei(source, AL_LOOPING, AL_FALSE); // need to implement this manually...
315
+
316
+ stream_to_buffer(buffers[0]);
317
+ stream_to_buffer(buffers[1]);
318
+
319
+ // TODO: Not good for songs with less than two buffers full of data.
320
+ alSourceQueueBuffers(source, 2, buffers);
321
+ alSourcePlay(source);
437
322
  }
438
323
 
439
324
  void stop() override
440
325
  {
441
- int source = lookup_source();
442
- if (source != ALChannelManagement::NO_SOURCE) {
443
- alSourceStop(source);
444
-
445
- ALuint buffer;
446
-
447
- // The number of QUEUED buffers apparently includes the number of
448
- // PROCESSED ones, so getting rid of the QUEUED ones is enough.
449
-
450
- int queued;
451
- alGetSourcei(source, AL_BUFFERS_QUEUED, &queued);
452
- while (queued--) {
453
- alSourceUnqueueBuffers(source, 1, &buffer);
454
- }
326
+ ALuint source = al_source_for_songs();
327
+
328
+ alSourceStop(source);
329
+
330
+ // Unqueue all buffers for this source.
331
+ // The number of QUEUED buffers apparently includes the number of PROCESSED ones,
332
+ // so getting rid of the QUEUED ones is enough.
333
+ ALuint buffer;
334
+ int queued;
335
+ alGetSourcei(source, AL_BUFFERS_QUEUED, &queued);
336
+ while (queued--) {
337
+ alSourceUnqueueBuffers(source, 1, &buffer);
455
338
  }
339
+
456
340
  file->rewind();
457
341
  }
458
342
 
459
343
  void pause() override
460
344
  {
461
- int source = lookup_source();
462
- if (source != ALChannelManagement::NO_SOURCE) {
463
- alSourcePause(source);
464
- }
345
+ alSourcePause(al_source_for_songs());
465
346
  }
466
347
 
467
348
  void resume() override
468
349
  {
469
- int source = lookup_source();
470
- if (source != ALChannelManagement::NO_SOURCE) {
471
- alSourcePlay(source);
472
- }
350
+ alSourcePlay(al_source_for_songs());
473
351
  }
474
352
 
475
353
  bool paused() const override
476
354
  {
477
- int source = lookup_source();
478
- if (source == ALChannelManagement::NO_SOURCE) {
479
- return false;
480
- }
481
355
  ALint state;
482
- alGetSourcei(source, AL_SOURCE_STATE, &state);
356
+ alGetSourcei(al_source_for_songs(), AL_SOURCE_STATE, &state);
483
357
  return state == AL_PAUSED;
484
358
  }
485
359
 
486
360
  void update() override
487
361
  {
488
- int source = lookup_source();
362
+ ALuint source = al_source_for_songs();
489
363
 
490
364
  ALuint buffer;
491
365
  int processed;
@@ -495,9 +369,7 @@ public:
495
369
  for (int i = 0; i < processed; ++i) {
496
370
  alSourceUnqueueBuffers(source, 1, &buffer);
497
371
  active = stream_to_buffer(buffer);
498
- if (active) {
499
- alSourceQueueBuffers(source, 1, &buffer);
500
- }
372
+ if (active) alSourceQueueBuffers(source, 1, &buffer);
501
373
  }
502
374
 
503
375
  ALint state;
@@ -528,23 +400,16 @@ Gosu::Song::Song(const string& filename)
528
400
  if (has_extension(filename, ".mp3") ||
529
401
  has_extension(filename, ".aac") ||
530
402
  has_extension(filename, ".m4a")) {
531
- CONSTRUCTOR_BEGIN;
532
403
  data.reset(new ModuleData(filename));
533
- CONSTRUCTOR_END;
534
- return;
535
404
  }
405
+ else
536
406
  #endif
537
-
538
- CONSTRUCTOR_BEGIN;
539
407
  data.reset(new StreamData(filename));
540
- CONSTRUCTOR_END;
541
408
  }
542
409
 
543
410
  Gosu::Song::Song(Reader reader)
544
411
  {
545
- CONSTRUCTOR_BEGIN;
546
412
  data.reset(new StreamData(reader));
547
- CONSTRUCTOR_END;
548
413
  }
549
414
 
550
415
  Gosu::Song::~Song()
@@ -579,7 +444,7 @@ void Gosu::Song::play(bool looping)
579
444
  void Gosu::Song::pause()
580
445
  {
581
446
  if (cur_song == this) {
582
- data->pause(); // may be redundant
447
+ data->pause();
583
448
  }
584
449
  }
585
450
 
@@ -606,9 +471,9 @@ double Gosu::Song::volume() const
606
471
  return data->volume();
607
472
  }
608
473
 
609
- void Gosu::Song::change_volume(double volume)
474
+ void Gosu::Song::set_volume(double volume)
610
475
  {
611
- data->change_volume(volume);
476
+ data->set_volume(volume);
612
477
  }
613
478
 
614
479
  void Gosu::Song::update()