We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Decimation of Some WAV Files Leads to Incorrect Trace Duration
### Avoid duplicates

- [X] I searched existing issues

### Bug Summary

When I read some WAV format files with Obspy and then issue decimate, the end time of the resultant Trace is incorrect. I have only seen this problem with 44100 Hz, 2 channel WAV files. I have seen the issue with audio formats in `pcm_s32le` and `pcm_s16le`.

I have only inspected the end time so I am currently unaware of any other effects that may be the actual cause of the incorrect end time.

### Code to Reproduce

```python
import obspy

# Decimating this WAV leads to problems
# https://www.lynxstudio.com/downloads/e44/sample-wav-file-zip-encoded-44-1khz-pcm-24-stereo/  # noqa
# filename from this Zip is SineWaveMinus16.wav
print('Testing SineWaveMinus16.wav')
st = obspy.read('SineWaveMinus16.wav', format='WAV')
print(st)
print(st.copy().decimate(2))
print(st.copy().decimate(4))
"""ffprobe of `SineWaveMinus16.wav`:
Input #0, wav, from 'SineWaveMinus16.wav':
  Duration: 00:01:00.00, bitrate: 2822 kb/s
  Stream #0:0: Audio: pcm_s32le ([1][0][0][0] / 0x0001), 44100 Hz, 2 channels,
                      s32, 2822 kb/s
"""

# These problems do not occur with all WAV files
# https://www2.cs.uic.edu/~i101/SoundFiles/StarWars60.wav
print('Testing StarWars60.wav')
st = obspy.read('StarWars60.wav', format='WAV')
print(st)
print(st.copy().decimate(2))
print(st.copy().decimate(4))
"""ffprobe of `StarWars60.wav`:
Input #0, wav, from 'StarWars60.wav':
  Duration: 00:01:00.00, bitrate: 352 kb/s
  Stream #0:0: Audio: pcm_s16le ([1][0][0][0] / 0x0001), 22050 Hz, 1 channels,
                      s16, 352 kb/s
"""

# Decimating this WAV leads to problems
# https://mauvecloud.net/sounds/pcm1644s.wav
print('Testing pcm1644s.wav')
st = obspy.read('pcm1644s.wav', format='WAV')
print(st)
print(st.copy().decimate(2))
print(st.copy().decimate(4))
"""ffprobe of `pcm1644s.wav`:
Input #0, wav, from 'pcm1644s.wav':
  Duration: 00:00:06.69, bitrate: 1411 kb/s
  Stream #0:0: Audio: pcm_s16le ([1][0][0][0] / 0x0001), 44100 Hz, 2 channels,
              s16, 1411 kb/s
"""

# Decimating this WAV does NOT lead to problems
# https://mauvecloud.net/sounds/pcm1644m.wav
print('Testing pcm1644m.wav')
st = obspy.read('pcm1644m.wav', format='WAV')
print(st)
print(st.copy().decimate(2))
print(st.copy().decimate(4))
"""ffprobe of `pcm1644m.wav`:
Input #0, wav, from 'pcm1644m.wav':
  Duration: 00:00:06.69, bitrate: 705 kb/s
  Stream #0:0: Audio: pcm_s16le ([1][0][0][0] / 0x0001), 44100 Hz, 1 channels,
               s16, 705 kb/s
"""
```


### Error Traceback

```sh
Testing SineWaveMinus16.wav
1 Trace(s) in Stream:
... | 1970-01-01T00:00:00.000000Z - 1970-01-01T00:00:59.999977Z | 44100.0 Hz, 2646000 samples
1 Trace(s) in Stream:
... | 1970-01-01T00:00:00.000000Z - 1970-01-01T00:01:59.999955Z | 22050.0 Hz, 2646000 samples
1 Trace(s) in Stream:
... | 1970-01-01T00:00:00.000000Z - 1970-01-01T00:01:59.999909Z | 11025.0 Hz, 1323000 samples
Testing StarWars60.wav
1 Trace(s) in Stream:
... | 1970-01-01T00:00:00.000000Z - 1970-01-01T00:00:59.999955Z | 22050.0 Hz, 1323000 samples
1 Trace(s) in Stream:
... | 1970-01-01T00:00:00.000000Z - 1970-01-01T00:00:59.999909Z | 11025.0 Hz, 661500 samples
1 Trace(s) in Stream:
... | 1970-01-01T00:00:00.000000Z - 1970-01-01T00:00:59.999819Z | 5512.5 Hz, 330750 samples
Testing pcm1644s.wav
1 Trace(s) in Stream:
... | 1970-01-01T00:00:00.000000Z - 1970-01-01T00:00:06.687256Z | 44100.0 Hz, 294909 samples
1 Trace(s) in Stream:
... | 1970-01-01T00:00:00.000000Z - 1970-01-01T00:00:13.374512Z | 22050.0 Hz, 294909 samples
1 Trace(s) in Stream:
... | 1970-01-01T00:00:00.000000Z - 1970-01-01T00:00:13.374512Z | 11025.0 Hz, 147455 samples
```


### ObsPy Version?

1.4.0

### Operating System?

Windows

### Python Version?

3.11.5

### Installation Method?

conda

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `obspy/io/wav/core.py`
Content:
```
1 #!/usr/bin/env python
2 # ----------------------------------------------------------------------
3 # Filename: core.py
4 #  Purpose: Python Class for transforming seismograms to audio WAV files
5 #   Author: Moritz Beyreuther
6 #    Email: moritz.beyreuther@geophysik.uni-muenchen.de
7 #
8 # Copyright (C) 2009-2012 Moritz Beyreuther
9 # ------------------------------------------------------------------------
10 """
11 WAV bindings to ObsPy core module.
12 
13 :copyright:
14     The ObsPy Development Team (devs@obspy.org)
15 :license:
16     GNU Lesser General Public License, Version 3
17     (https://www.gnu.org/copyleft/lesser.html)
18 """
19 from pathlib import Path
20 import wave
21 
22 import numpy as np
23 
24 from obspy import Stream, Trace
25 from obspy.core.compatibility import from_buffer
26 
27 
28 # WAVE data format is unsigned char up to 8bit, and signed int
29 # for the remaining.
30 WIDTH2DTYPE = {
31     1: '<u1',  # unsigned char
32     2: '<i2',  # signed short int
33     4: '<i4',  # signed int (int32)
34 }
35 
36 
37 def _is_wav(filename):
38     """
39     Checks whether a file is a audio WAV file or not.
40 
41     :type filename: str
42     :param filename: Name of the audio WAV file to be checked.
43     :rtype: bool
44     :return: ``True`` if a WAV file.
45 
46     .. rubric:: Example
47 
48     >>> _is_wav("/path/to/3cssan.near.8.1.RNON.wav")  #doctest: +SKIP
49     True
50     """
51     try:
52         fh = wave.open(filename, 'rb')
53         try:
54             (_nchannel, width, _rate, _len, _comptype, _compname) = \
55                 fh.getparams()
56         finally:
57             fh.close()
58     except Exception:
59         return False
60     if width in [1, 2, 4]:
61         return True
62     return False
63 
64 
65 def _read_wav(filename, headonly=False, **kwargs):  # @UnusedVariable
66     """
67     Reads a audio WAV file and returns an ObsPy Stream object.
68 
69     Currently supports uncompressed unsigned char and short integer and
70     integer data values. This should cover most WAV files.
71 
72     .. warning::
73         This function should NOT be called directly, it registers via the
74         ObsPy :func:`~obspy.core.stream.read` function, call this instead.
75 
76     :type filename: str
77     :param filename: Audio WAV file to be read.
78     :rtype: :class:`~obspy.core.stream.Stream`
79     :return: A ObsPy Stream object.
80 
81     .. rubric:: Example
82 
83     >>> from obspy import read
84     >>> st = read("/path/to/3cssan.near.8.1.RNON.wav")
85     >>> print(st) #doctest: +NORMALIZE_WHITESPACE
86     1 Trace(s) in Stream:
87     ... | 1970-01-01T00:00:00.000000Z - 1970-01-01T00:00:00.371143Z
88     | 7000.0 Hz, 2599 samples
89     """
90     # read WAV file
91     fh = wave.open(filename, 'rb')
92     try:
93         # header information
94         (_nchannel, width, rate, length, _comptype, _compname) = fh.getparams()
95         header = {'sampling_rate': rate, 'npts': length}
96         if headonly:
97             return Stream([Trace(header=header)])
98         if width not in WIDTH2DTYPE.keys():
99             msg = "Unsupported Format Type, word width %dbytes" % width
100             raise TypeError(msg)
101         data = from_buffer(fh.readframes(length), dtype=WIDTH2DTYPE[width])
102     finally:
103         fh.close()
104     return Stream([Trace(header=header, data=data)])
105 
106 
107 def _write_wav(stream, filename, framerate=7000, rescale=False, width=None,
108                **kwargs):  # @UnusedVariable
109     """
110     Writes a audio WAV file from given ObsPy Stream object. The seismogram is
111     squeezed to audible frequencies.
112 
113     The generated WAV sound file is as a result really short. The data
114     are written uncompressed as signed 4-byte integers.
115 
116     .. warning::
117         This function should NOT be called directly, it registers via the
118         the :meth:`~obspy.core.stream.Stream.write` method of an
119         ObsPy :class:`~obspy.core.stream.Stream` object, call this instead.
120 
121     :type stream: :class:`~obspy.core.stream.Stream`
122     :param stream: The ObsPy Stream object to write.
123     :type filename: str
124     :param filename: Name of the audio WAV file to write.
125     :type framerate: int, optional
126     :param framerate: Sample rate of WAV file to use. This this will squeeze
127         the seismogram (default is 7000).
128     :type rescale: bool, optional
129     :param rescale: Maximum to maximal representable number
130     :type width: int, optional
131     :param width: dtype to write, 1 for '<u1', 2 for '<i2' or 4 for '<i4'.
132         tries to autodetect width from data, uses 4 otherwise
133     """
134     i = 0
135     file_path = Path(filename)
136     base = file_path.parent / file_path.stem
137     if width not in WIDTH2DTYPE.keys() and width is not None:
138         raise TypeError("Unsupported Format Type, word width %dbytes" % width)
139     for trace in stream:
140         # try to autodetect width from data, see #791
141         if width is None:
142             if trace.data.dtype.str[-2:] in ['u1', 'i2', 'i4']:
143                 tr_width = int(trace.data.dtype.str[-1])
144             else:
145                 tr_width = 4
146         else:
147             tr_width = width
148         # write WAV file
149         if len(stream) >= 2:
150             filename = "%s%03d%s" % (base, i, file_path.suffix)
151         w = wave.open(filename, 'wb')
152         try:
153             trace.stats.npts = len(trace.data)
154             # (nchannels, sampwidth, framerate, nframes, comptype, compname)
155             w.setparams((1, tr_width, framerate, trace.stats.npts, 'NONE',
156                          'not compressed'))
157             data = trace.data
158             dtype = WIDTH2DTYPE[tr_width]
159             if rescale:
160                 # optimal scale, account for +/- and the zero
161                 maxint = 2 ** (tr_width * 8 - 1) - 1
162                 # upcast for following rescaling
163                 data = data.astype(np.float64)
164                 data = data / abs(data).max() * maxint
165             data = np.require(data, dtype=dtype)
166             w.writeframes(data.tobytes())
167         finally:
168             w.close()
169         i += 1
170 
171 
172 if __name__ == '__main__':
173     import doctest
174     doctest.testmod(exclude_empty=True)
175
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/obspy/io/wav/core.py b/obspy/io/wav/core.py
--- a/obspy/io/wav/core.py
+++ b/obspy/io/wav/core.py
@@ -88,20 +88,19 @@
     | 7000.0 Hz, 2599 samples
     """
     # read WAV file
-    fh = wave.open(filename, 'rb')
-    try:
+    with wave.open(filename, 'rb') as fh:
         # header information
-        (_nchannel, width, rate, length, _comptype, _compname) = fh.getparams()
+        (nchannel, width, rate, length, _comptype, _compname) = fh.getparams()
         header = {'sampling_rate': rate, 'npts': length}
         if headonly:
-            return Stream([Trace(header=header)])
+            return Stream([Trace(header=header) for _ in range(nchannel)])
         if width not in WIDTH2DTYPE.keys():
             msg = "Unsupported Format Type, word width %dbytes" % width
             raise TypeError(msg)
         data = from_buffer(fh.readframes(length), dtype=WIDTH2DTYPE[width])
-    finally:
-        fh.close()
-    return Stream([Trace(header=header, data=data)])
+    traces = [Trace(header=header, data=data[i::nchannel])
+              for i in range(nchannel)]
+    return Stream(traces)
 
 
 def _write_wav(stream, filename, framerate=7000, rescale=False, width=None,

{"golden_diff": "diff --git a/obspy/io/wav/core.py b/obspy/io/wav/core.py\n--- a/obspy/io/wav/core.py\n+++ b/obspy/io/wav/core.py\n@@ -88,20 +88,19 @@\n     | 7000.0 Hz, 2599 samples\n     \"\"\"\n     # read WAV file\n-    fh = wave.open(filename, 'rb')\n-    try:\n+    with wave.open(filename, 'rb') as fh:\n         # header information\n-        (_nchannel, width, rate, length, _comptype, _compname) = fh.getparams()\n+        (nchannel, width, rate, length, _comptype, _compname) = fh.getparams()\n         header = {'sampling_rate': rate, 'npts': length}\n         if headonly:\n-            return Stream([Trace(header=header)])\n+            return Stream([Trace(header=header) for _ in range(nchannel)])\n         if width not in WIDTH2DTYPE.keys():\n             msg = \"Unsupported Format Type, word width %dbytes\" % width\n             raise TypeError(msg)\n         data = from_buffer(fh.readframes(length), dtype=WIDTH2DTYPE[width])\n-    finally:\n-        fh.close()\n-    return Stream([Trace(header=header, data=data)])\n+    traces = [Trace(header=header, data=data[i::nchannel])\n+              for i in range(nchannel)]\n+    return Stream(traces)\n \n \n def _write_wav(stream, filename, framerate=7000, rescale=False, width=None,\n", "issue": "Decimation of Some WAV Files Leads to Incorrect Trace Duration\n### Avoid duplicates\r\n\r\n- [X] I searched existing issues\r\n\r\n### Bug Summary\r\n\r\nWhen I read some WAV format files with Obspy and then issue decimate, the end time of the resultant Trace is incorrect. I have only seen this problem with 44100 Hz, 2 channel WAV files. I have seen the issue with audio formats in `pcm_s32le` and `pcm_s16le`.\r\n\r\nI have only inspected the end time so I am currently unaware of any other effects that may be the actual cause of the incorrect end time.\r\n\r\n### Code to Reproduce\r\n\r\n```python\r\nimport obspy\r\n\r\n# Decimating this WAV leads to problems\r\n# https://www.lynxstudio.com/downloads/e44/sample-wav-file-zip-encoded-44-1khz-pcm-24-stereo/  # noqa\r\n# filename from this Zip is SineWaveMinus16.wav\r\nprint('Testing SineWaveMinus16.wav')\r\nst = obspy.read('SineWaveMinus16.wav', format='WAV')\r\nprint(st)\r\nprint(st.copy().decimate(2))\r\nprint(st.copy().decimate(4))\r\n\"\"\"ffprobe of `SineWaveMinus16.wav`:\r\nInput #0, wav, from 'SineWaveMinus16.wav':\r\n  Duration: 00:01:00.00, bitrate: 2822 kb/s\r\n  Stream #0:0: Audio: pcm_s32le ([1][0][0][0] / 0x0001), 44100 Hz, 2 channels,\r\n                      s32, 2822 kb/s\r\n\"\"\"\r\n\r\n# These problems do not occur with all WAV files\r\n# https://www2.cs.uic.edu/~i101/SoundFiles/StarWars60.wav\r\nprint('Testing StarWars60.wav')\r\nst = obspy.read('StarWars60.wav', format='WAV')\r\nprint(st)\r\nprint(st.copy().decimate(2))\r\nprint(st.copy().decimate(4))\r\n\"\"\"ffprobe of `StarWars60.wav`:\r\nInput #0, wav, from 'StarWars60.wav':\r\n  Duration: 00:01:00.00, bitrate: 352 kb/s\r\n  Stream #0:0: Audio: pcm_s16le ([1][0][0][0] / 0x0001), 22050 Hz, 1 channels,\r\n                      s16, 352 kb/s\r\n\"\"\"\r\n\r\n# Decimating this WAV leads to problems\r\n# https://mauvecloud.net/sounds/pcm1644s.wav\r\nprint('Testing pcm1644s.wav')\r\nst = obspy.read('pcm1644s.wav', format='WAV')\r\nprint(st)\r\nprint(st.copy().decimate(2))\r\nprint(st.copy().decimate(4))\r\n\"\"\"ffprobe of `pcm1644s.wav`:\r\nInput #0, wav, from 'pcm1644s.wav':\r\n  Duration: 00:00:06.69, bitrate: 1411 kb/s\r\n  Stream #0:0: Audio: pcm_s16le ([1][0][0][0] / 0x0001), 44100 Hz, 2 channels,\r\n              s16, 1411 kb/s\r\n\"\"\"\r\n\r\n# Decimating this WAV does NOT lead to problems\r\n# https://mauvecloud.net/sounds/pcm1644m.wav\r\nprint('Testing pcm1644m.wav')\r\nst = obspy.read('pcm1644m.wav', format='WAV')\r\nprint(st)\r\nprint(st.copy().decimate(2))\r\nprint(st.copy().decimate(4))\r\n\"\"\"ffprobe of `pcm1644m.wav`:\r\nInput #0, wav, from 'pcm1644m.wav':\r\n  Duration: 00:00:06.69, bitrate: 705 kb/s\r\n  Stream #0:0: Audio: pcm_s16le ([1][0][0][0] / 0x0001), 44100 Hz, 1 channels,\r\n               s16, 705 kb/s\r\n\"\"\"\r\n```\r\n\r\n\r\n### Error Traceback\r\n\r\n```sh\r\nTesting SineWaveMinus16.wav\r\n1 Trace(s) in Stream:\r\n... | 1970-01-01T00:00:00.000000Z - 1970-01-01T00:00:59.999977Z | 44100.0 Hz, 2646000 samples\r\n1 Trace(s) in Stream:\r\n... | 1970-01-01T00:00:00.000000Z - 1970-01-01T00:01:59.999955Z | 22050.0 Hz, 2646000 samples\r\n1 Trace(s) in Stream:\r\n... | 1970-01-01T00:00:00.000000Z - 1970-01-01T00:01:59.999909Z | 11025.0 Hz, 1323000 samples\r\nTesting StarWars60.wav\r\n1 Trace(s) in Stream:\r\n... | 1970-01-01T00:00:00.000000Z - 1970-01-01T00:00:59.999955Z | 22050.0 Hz, 1323000 samples\r\n1 Trace(s) in Stream:\r\n... | 1970-01-01T00:00:00.000000Z - 1970-01-01T00:00:59.999909Z | 11025.0 Hz, 661500 samples\r\n1 Trace(s) in Stream:\r\n... | 1970-01-01T00:00:00.000000Z - 1970-01-01T00:00:59.999819Z | 5512.5 Hz, 330750 samples\r\nTesting pcm1644s.wav\r\n1 Trace(s) in Stream:\r\n... | 1970-01-01T00:00:00.000000Z - 1970-01-01T00:00:06.687256Z | 44100.0 Hz, 294909 samples\r\n1 Trace(s) in Stream:\r\n... | 1970-01-01T00:00:00.000000Z - 1970-01-01T00:00:13.374512Z | 22050.0 Hz, 294909 samples\r\n1 Trace(s) in Stream:\r\n... | 1970-01-01T00:00:00.000000Z - 1970-01-01T00:00:13.374512Z | 11025.0 Hz, 147455 samples\r\n```\r\n\r\n\r\n### ObsPy Version?\r\n\r\n1.4.0\r\n\r\n### Operating System?\r\n\r\nWindows\r\n\r\n### Python Version?\r\n\r\n3.11.5\r\n\r\n### Installation Method?\r\n\r\nconda\n", "before_files": [{"content": "#!/usr/bin/env python\n# ----------------------------------------------------------------------\n# Filename: core.py\n#  Purpose: Python Class for transforming seismograms to audio WAV files\n#   Author: Moritz Beyreuther\n#    Email: moritz.beyreuther@geophysik.uni-muenchen.de\n#\n# Copyright (C) 2009-2012 Moritz Beyreuther\n# ------------------------------------------------------------------------\n\"\"\"\nWAV bindings to ObsPy core module.\n\n:copyright:\n    The ObsPy Development Team (devs@obspy.org)\n:license:\n    GNU Lesser General Public License, Version 3\n    (https://www.gnu.org/copyleft/lesser.html)\n\"\"\"\nfrom pathlib import Path\nimport wave\n\nimport numpy as np\n\nfrom obspy import Stream, Trace\nfrom obspy.core.compatibility import from_buffer\n\n\n# WAVE data format is unsigned char up to 8bit, and signed int\n# for the remaining.\nWIDTH2DTYPE = {\n    1: '<u1',  # unsigned char\n    2: '<i2',  # signed short int\n    4: '<i4',  # signed int (int32)\n}\n\n\ndef _is_wav(filename):\n    \"\"\"\n    Checks whether a file is a audio WAV file or not.\n\n    :type filename: str\n    :param filename: Name of the audio WAV file to be checked.\n    :rtype: bool\n    :return: ``True`` if a WAV file.\n\n    .. rubric:: Example\n\n    >>> _is_wav(\"/path/to/3cssan.near.8.1.RNON.wav\")  #doctest: +SKIP\n    True\n    \"\"\"\n    try:\n        fh = wave.open(filename, 'rb')\n        try:\n            (_nchannel, width, _rate, _len, _comptype, _compname) = \\\n                fh.getparams()\n        finally:\n            fh.close()\n    except Exception:\n        return False\n    if width in [1, 2, 4]:\n        return True\n    return False\n\n\ndef _read_wav(filename, headonly=False, **kwargs):  # @UnusedVariable\n    \"\"\"\n    Reads a audio WAV file and returns an ObsPy Stream object.\n\n    Currently supports uncompressed unsigned char and short integer and\n    integer data values. This should cover most WAV files.\n\n    .. warning::\n        This function should NOT be called directly, it registers via the\n        ObsPy :func:`~obspy.core.stream.read` function, call this instead.\n\n    :type filename: str\n    :param filename: Audio WAV file to be read.\n    :rtype: :class:`~obspy.core.stream.Stream`\n    :return: A ObsPy Stream object.\n\n    .. rubric:: Example\n\n    >>> from obspy import read\n    >>> st = read(\"/path/to/3cssan.near.8.1.RNON.wav\")\n    >>> print(st) #doctest: +NORMALIZE_WHITESPACE\n    1 Trace(s) in Stream:\n    ... | 1970-01-01T00:00:00.000000Z - 1970-01-01T00:00:00.371143Z\n    | 7000.0 Hz, 2599 samples\n    \"\"\"\n    # read WAV file\n    fh = wave.open(filename, 'rb')\n    try:\n        # header information\n        (_nchannel, width, rate, length, _comptype, _compname) = fh.getparams()\n        header = {'sampling_rate': rate, 'npts': length}\n        if headonly:\n            return Stream([Trace(header=header)])\n        if width not in WIDTH2DTYPE.keys():\n            msg = \"Unsupported Format Type, word width %dbytes\" % width\n            raise TypeError(msg)\n        data = from_buffer(fh.readframes(length), dtype=WIDTH2DTYPE[width])\n    finally:\n        fh.close()\n    return Stream([Trace(header=header, data=data)])\n\n\ndef _write_wav(stream, filename, framerate=7000, rescale=False, width=None,\n               **kwargs):  # @UnusedVariable\n    \"\"\"\n    Writes a audio WAV file from given ObsPy Stream object. The seismogram is\n    squeezed to audible frequencies.\n\n    The generated WAV sound file is as a result really short. The data\n    are written uncompressed as signed 4-byte integers.\n\n    .. warning::\n        This function should NOT be called directly, it registers via the\n        the :meth:`~obspy.core.stream.Stream.write` method of an\n        ObsPy :class:`~obspy.core.stream.Stream` object, call this instead.\n\n    :type stream: :class:`~obspy.core.stream.Stream`\n    :param stream: The ObsPy Stream object to write.\n    :type filename: str\n    :param filename: Name of the audio WAV file to write.\n    :type framerate: int, optional\n    :param framerate: Sample rate of WAV file to use. This this will squeeze\n        the seismogram (default is 7000).\n    :type rescale: bool, optional\n    :param rescale: Maximum to maximal representable number\n    :type width: int, optional\n    :param width: dtype to write, 1 for '<u1', 2 for '<i2' or 4 for '<i4'.\n        tries to autodetect width from data, uses 4 otherwise\n    \"\"\"\n    i = 0\n    file_path = Path(filename)\n    base = file_path.parent / file_path.stem\n    if width not in WIDTH2DTYPE.keys() and width is not None:\n        raise TypeError(\"Unsupported Format Type, word width %dbytes\" % width)\n    for trace in stream:\n        # try to autodetect width from data, see #791\n        if width is None:\n            if trace.data.dtype.str[-2:] in ['u1', 'i2', 'i4']:\n                tr_width = int(trace.data.dtype.str[-1])\n            else:\n                tr_width = 4\n        else:\n            tr_width = width\n        # write WAV file\n        if len(stream) >= 2:\n            filename = \"%s%03d%s\" % (base, i, file_path.suffix)\n        w = wave.open(filename, 'wb')\n        try:\n            trace.stats.npts = len(trace.data)\n            # (nchannels, sampwidth, framerate, nframes, comptype, compname)\n            w.setparams((1, tr_width, framerate, trace.stats.npts, 'NONE',\n                         'not compressed'))\n            data = trace.data\n            dtype = WIDTH2DTYPE[tr_width]\n            if rescale:\n                # optimal scale, account for +/- and the zero\n                maxint = 2 ** (tr_width * 8 - 1) - 1\n                # upcast for following rescaling\n                data = data.astype(np.float64)\n                data = data / abs(data).max() * maxint\n            data = np.require(data, dtype=dtype)\n            w.writeframes(data.tobytes())\n        finally:\n            w.close()\n        i += 1\n\n\nif __name__ == '__main__':\n    import doctest\n    doctest.testmod(exclude_empty=True)\n", "path": "obspy/io/wav/core.py"}], "after_files": [{"content": "#!/usr/bin/env python\n# ----------------------------------------------------------------------\n# Filename: core.py\n#  Purpose: Python Class for transforming seismograms to audio WAV files\n#   Author: Moritz Beyreuther\n#    Email: moritz.beyreuther@geophysik.uni-muenchen.de\n#\n# Copyright (C) 2009-2012 Moritz Beyreuther\n# ------------------------------------------------------------------------\n\"\"\"\nWAV bindings to ObsPy core module.\n\n:copyright:\n    The ObsPy Development Team (devs@obspy.org)\n:license:\n    GNU Lesser General Public License, Version 3\n    (https://www.gnu.org/copyleft/lesser.html)\n\"\"\"\nfrom pathlib import Path\nimport wave\n\nimport numpy as np\n\nfrom obspy import Stream, Trace\nfrom obspy.core.compatibility import from_buffer\n\n\n# WAVE data format is unsigned char up to 8bit, and signed int\n# for the remaining.\nWIDTH2DTYPE = {\n    1: '<u1',  # unsigned char\n    2: '<i2',  # signed short int\n    4: '<i4',  # signed int (int32)\n}\n\n\ndef _is_wav(filename):\n    \"\"\"\n    Checks whether a file is a audio WAV file or not.\n\n    :type filename: str\n    :param filename: Name of the audio WAV file to be checked.\n    :rtype: bool\n    :return: ``True`` if a WAV file.\n\n    .. rubric:: Example\n\n    >>> _is_wav(\"/path/to/3cssan.near.8.1.RNON.wav\")  #doctest: +SKIP\n    True\n    \"\"\"\n    try:\n        fh = wave.open(filename, 'rb')\n        try:\n            (_nchannel, width, _rate, _len, _comptype, _compname) = \\\n                fh.getparams()\n        finally:\n            fh.close()\n    except Exception:\n        return False\n    if width in [1, 2, 4]:\n        return True\n    return False\n\n\ndef _read_wav(filename, headonly=False, **kwargs):  # @UnusedVariable\n    \"\"\"\n    Reads a audio WAV file and returns an ObsPy Stream object.\n\n    Currently supports uncompressed unsigned char and short integer and\n    integer data values. This should cover most WAV files.\n\n    .. warning::\n        This function should NOT be called directly, it registers via the\n        ObsPy :func:`~obspy.core.stream.read` function, call this instead.\n\n    :type filename: str\n    :param filename: Audio WAV file to be read.\n    :rtype: :class:`~obspy.core.stream.Stream`\n    :return: A ObsPy Stream object.\n\n    .. rubric:: Example\n\n    >>> from obspy import read\n    >>> st = read(\"/path/to/3cssan.near.8.1.RNON.wav\")\n    >>> print(st) #doctest: +NORMALIZE_WHITESPACE\n    1 Trace(s) in Stream:\n    ... | 1970-01-01T00:00:00.000000Z - 1970-01-01T00:00:00.371143Z\n    | 7000.0 Hz, 2599 samples\n    \"\"\"\n    # read WAV file\n    with wave.open(filename, 'rb') as fh:\n        # header information\n        (nchannel, width, rate, length, _comptype, _compname) = fh.getparams()\n        header = {'sampling_rate': rate, 'npts': length}\n        if headonly:\n            return Stream([Trace(header=header) for _ in range(nchannel)])\n        if width not in WIDTH2DTYPE.keys():\n            msg = \"Unsupported Format Type, word width %dbytes\" % width\n            raise TypeError(msg)\n        data = from_buffer(fh.readframes(length), dtype=WIDTH2DTYPE[width])\n    traces = [Trace(header=header, data=data[i::nchannel])\n              for i in range(nchannel)]\n    return Stream(traces)\n\n\ndef _write_wav(stream, filename, framerate=7000, rescale=False, width=None,\n               **kwargs):  # @UnusedVariable\n    \"\"\"\n    Writes a audio WAV file from given ObsPy Stream object. The seismogram is\n    squeezed to audible frequencies.\n\n    The generated WAV sound file is as a result really short. The data\n    are written uncompressed as signed 4-byte integers.\n\n    .. warning::\n        This function should NOT be called directly, it registers via the\n        the :meth:`~obspy.core.stream.Stream.write` method of an\n        ObsPy :class:`~obspy.core.stream.Stream` object, call this instead.\n\n    :type stream: :class:`~obspy.core.stream.Stream`\n    :param stream: The ObsPy Stream object to write.\n    :type filename: str\n    :param filename: Name of the audio WAV file to write.\n    :type framerate: int, optional\n    :param framerate: Sample rate of WAV file to use. This this will squeeze\n        the seismogram (default is 7000).\n    :type rescale: bool, optional\n    :param rescale: Maximum to maximal representable number\n    :type width: int, optional\n    :param width: dtype to write, 1 for '<u1', 2 for '<i2' or 4 for '<i4'.\n        tries to autodetect width from data, uses 4 otherwise\n    \"\"\"\n    i = 0\n    file_path = Path(filename)\n    base = file_path.parent / file_path.stem\n    if width not in WIDTH2DTYPE.keys() and width is not None:\n        raise TypeError(\"Unsupported Format Type, word width %dbytes\" % width)\n    for trace in stream:\n        # try to autodetect width from data, see #791\n        if width is None:\n            if trace.data.dtype.str[-2:] in ['u1', 'i2', 'i4']:\n                tr_width = int(trace.data.dtype.str[-1])\n            else:\n                tr_width = 4\n        else:\n            tr_width = width\n        # write WAV file\n        if len(stream) >= 2:\n            filename = \"%s%03d%s\" % (base, i, file_path.suffix)\n        w = wave.open(filename, 'wb')\n        try:\n            trace.stats.npts = len(trace.data)\n            # (nchannels, sampwidth, framerate, nframes, comptype, compname)\n            w.setparams((1, tr_width, framerate, trace.stats.npts, 'NONE',\n                         'not compressed'))\n            data = trace.data\n            dtype = WIDTH2DTYPE[tr_width]\n            if rescale:\n                # optimal scale, account for +/- and the zero\n                maxint = 2 ** (tr_width * 8 - 1) - 1\n                # upcast for following rescaling\n                data = data.astype(np.float64)\n                data = data / abs(data).max() * maxint\n            data = np.require(data, dtype=dtype)\n            w.writeframes(data.tobytes())\n        finally:\n            w.close()\n        i += 1\n\n\nif __name__ == '__main__':\n    import doctest\n    doctest.testmod(exclude_empty=True)\n", "path": "obspy/io/wav/core.py"}]}