diff --git a/MicroPython_BUILD/components/micropython/esp32/machine_i2s.c b/MicroPython_BUILD/components/micropython/esp32/machine_i2s.c new file mode 100644 index 0000000..a09a506 --- /dev/null +++ b/MicroPython_BUILD/components/micropython/esp32/machine_i2s.c @@ -0,0 +1,303 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 M5Stack (https://github.com/m5stack) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/obj.h" +#include "driver/i2s.h" +// #include "machine_i2s.h" +#include "py/runtime.h" +#include "py/mperrno.h" + +typedef enum { + I2S, I2S_MERUS, DAC_BUILT_IN, PDM +} output_mode_t; + + +typedef struct { + mp_obj_base_t base; + i2s_port_t port; + output_mode_t output_mode; + i2s_config_t config; + i2s_channel_t num_channels; + uint8_t volume; +} machine_i2s_obj_t; + +extern const mp_obj_type_t machine_i2s_type; + + +STATIC void machine_i2s_obj_init_helper(machine_i2s_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_mode, ARG_bclk, ARG_ws, ARG_data_out, ARG_data_in, ARG_samplerate, ARG_bitdepth, ARG_channel, ARG_volume}; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_mode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DAC_BUILT_IN} }, + { MP_QSTR_bclk, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 2} }, + { MP_QSTR_ws, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 13} }, + { MP_QSTR_data_out, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 5} }, + { MP_QSTR_data_in, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_samplerate, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 48000} }, + { MP_QSTR_bitdepth, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 16} }, + { MP_QSTR_channel, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 2} }, + { MP_QSTR_volume, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 80} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + output_mode_t mode = args[ARG_mode].u_int; + self->output_mode = mode; + self->config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | (mode == DAC_BUILT_IN ? I2S_MODE_DAC_BUILT_IN : 0)); + self->config.sample_rate = args[ARG_samplerate].u_int; + self->config.bits_per_sample = args[ARG_bitdepth].u_int; + self->config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT; + self->config.communication_format = (i2s_comm_format_t)((mode == DAC_BUILT_IN ? 0 : I2S_COMM_FORMAT_I2S) | I2S_COMM_FORMAT_I2S_MSB); + self->config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, //Interrupt level 1 + self->config.dma_buf_count = 4; + self->config.dma_buf_len = 1024; + self->config.use_apll = 0; + self->num_channels = args[ARG_channel].u_int; + self->volume = args[ARG_volume].u_int; + + if (i2s_driver_install(self->port, &self->config, 0, NULL) != ESP_OK) { + mp_raise_ValueError("I2S: failed to enable"); + } + + if (mode == DAC_BUILT_IN) { + i2s_set_pin(self->port, NULL); + i2s_set_dac_mode(I2S_DAC_CHANNEL_RIGHT_EN); + } else { + i2s_pin_config_t pin_config = { + .bck_io_num = args[ARG_bclk].u_int, + .ws_io_num = args[ARG_ws].u_int, + .data_out_num = args[ARG_data_out].u_int, + .data_in_num = args[ARG_data_in].u_int, + }; + if (i2s_set_pin(self->port, &pin_config) != ESP_OK) { + i2s_driver_uninstall(self->port); + mp_raise_ValueError("I2S: failed to set pins in"); + } + } + + i2s_set_clk(self->port, self->config.sample_rate, self->config.bits_per_sample, self->num_channels); + // i2s_zero_dma_buffer(self->port); +} + +//---------------------------------------------------------------------- +STATIC mp_obj_t machine_i2s_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_int_t port = I2S_NUM_0; + if (n_args > 0) { + port = mp_obj_get_int(args[0]); + n_args--; + args++; + } + + if (port >= I2S_NUM_MAX || port < I2S_NUM_0) { + mp_raise_ValueError("invalid I2S peripheral"); + } + + // Create I2S object + machine_i2s_obj_t *self = m_new_obj(machine_i2s_obj_t); + self->base.type = &machine_i2s_type; + self->port = port; + + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + machine_i2s_obj_init_helper(self, n_args, args, &kw_args); + return (machine_i2s_obj_t*)self; // discard const +} + +//---------------------------------------------------------------------- +STATIC mp_obj_t machine_i2s_obj_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + machine_i2s_obj_init_helper(args[0], n_args - 1, args + 1, kw_args); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(machine_i2s_init_obj, 1, machine_i2s_obj_init); + +//---------------------------------------------------------------------- +STATIC mp_obj_t machine_i2s_obj_deinit(mp_obj_t self_in) { + const machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in); + i2s_driver_uninstall(self->port); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(machine_i2s_deinit_obj, machine_i2s_obj_deinit); + +//----------------------------------------------------------------------- +STATIC mp_obj_t machine_i2s_obj_sample_rates(mp_obj_t self_in, mp_obj_t rate) { + machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in); + + self->config.sample_rate = mp_obj_get_int(rate); + i2s_set_sample_rates(self->port, (uint32_t)(self->config.sample_rate)); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(machine_i2s_set_sample_rates_obj, machine_i2s_obj_sample_rates); + +//----------------------------------------------------------------------- +STATIC mp_obj_t machine_i2s_obj_bits_per_sample(mp_obj_t self_in, mp_obj_t bits) { + machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in); + + self->config.bits_per_sample = (i2s_bits_per_sample_t)mp_obj_get_int(bits); + i2s_set_clk(self->port, self->config.sample_rate, self->config.bits_per_sample, self->num_channels); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(machine_i2s_obj_bits_per_sample_obj, machine_i2s_obj_bits_per_sample); + +//----------------------------------------------------------------------- +STATIC mp_obj_t machine_i2s_obj_set_channel(mp_obj_t self_in, mp_obj_t channel) { + machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in); + + self->num_channels = (i2s_channel_t)mp_obj_get_int(channel); + i2s_set_clk(self->port, self->config.sample_rate, self->config.bits_per_sample, self->num_channels); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(machine_i2s_obj_set_channel_obj, machine_i2s_obj_set_channel); + +//----------------------------------------------------------------------- +STATIC mp_obj_t machine_i2s_obj_set_volume(mp_obj_t self_in, mp_obj_t volume) { + machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in); + + self->volume = mp_obj_get_int(volume); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(machine_i2s_obj_set_volume_obj, machine_i2s_obj_set_volume); + +//---------------------------------------------------------------------- +STATIC mp_obj_t machine_i2s_obj_write(const mp_obj_t self_in, mp_obj_t buf_in) { + const machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in); + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_READ); + + if (i2s_write_bytes(self->port, bufinfo.buf, bufinfo.len, portMAX_DELAY) == ESP_FAIL) { + mp_raise_OSError(MP_EIO); + } + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(machine_i2s_write_obj, machine_i2s_obj_write); + +//---------------------------------------------------------------------- +STATIC mp_obj_t machine_i2s_obj_stream_out(const mp_obj_t self_in, mp_obj_t buf_in) { + const machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in); + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_READ); + + uint8_t buf_bytes_per_sample = (self->config.bits_per_sample / 8); + uint32_t num_samples = bufinfo.len / buf_bytes_per_sample / self->num_channels; + + // pointer to left / right sample position + char *ptr_l = bufinfo.buf; + char *ptr_r = bufinfo.buf + buf_bytes_per_sample; + uint8_t stride = buf_bytes_per_sample * 2; + + if (self->num_channels == (i2s_channel_t)1) { + ptr_r = ptr_l; + } + + int bytes_pushed = 0; + TickType_t max_wait = 20 / portTICK_PERIOD_MS; // portMAX_DELAY = bad idea + for (int i = 0; i < num_samples; i++) { + + if(self->output_mode == DAC_BUILT_IN) { + // assume 16 bit src bit_depth + short left = *(short *) ptr_l; + short right = *(short *) ptr_r; + left = (int)left * self->volume / 100; + right = (int)right * self->volume / 100; + + // The built-in DAC wants unsigned samples, so we shift the range + // from -32768-32767 to 0-65535. + left = left + 0x8000; + right = right + 0x8000; + + uint32_t sample = (uint16_t) left; + sample = (sample << 16 & 0xffff0000) | ((uint16_t) right); + + bytes_pushed = i2s_push_sample(self->port, (const char*) &sample, max_wait); + } else { + + switch (self->config.bits_per_sample) + { + case I2S_BITS_PER_SAMPLE_16BIT: + ; // workaround + + /* low - high / low - high */ + const char samp32[4] = {ptr_l[0], ptr_l[1], ptr_r[0], ptr_r[1]}; + + bytes_pushed = i2s_push_sample(self->port, (const char*) &samp32, max_wait); + break; + + case I2S_BITS_PER_SAMPLE_32BIT: + ; // workaround + + const char samp64[8] = {0, 0, ptr_l[0], ptr_l[1], 0, 0, ptr_r[0], ptr_r[1]}; + bytes_pushed = i2s_push_sample(self->port, (const char*) &samp64, max_wait); + break; + + default: + // ESP_LOGE(TAG, "bit depth unsupported: %d", self->config.bits_per_sample); + mp_raise_ValueError("8 bit depth unsupported"); + } + } + + // DMA buffer full - retry + if (bytes_pushed == 0) { + i--; + } else { + ptr_r += stride; + ptr_l += stride; + } + } + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(machine_i2s_obj_stream_out_obj, machine_i2s_obj_stream_out); + +//---------------------------------------------------------------------- +STATIC const mp_rom_map_elem_t machine_i2s_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_i2s_init_obj)}, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_i2s_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_sample_rate), MP_ROM_PTR(&machine_i2s_set_sample_rates_obj) }, + { MP_ROM_QSTR(MP_QSTR_bits_per_sample), MP_ROM_PTR(&machine_i2s_obj_bits_per_sample_obj) }, + { MP_ROM_QSTR(MP_QSTR_channel), MP_ROM_PTR(&machine_i2s_obj_set_channel_obj) }, + { MP_ROM_QSTR(MP_QSTR_volume), MP_ROM_PTR(&machine_i2s_obj_set_volume_obj) }, + { MP_ROM_QSTR(MP_QSTR_stream_out), MP_ROM_PTR(&machine_i2s_obj_stream_out_obj) }, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&machine_i2s_write_obj) }, + + { MP_ROM_QSTR(MP_QSTR_MODE_IN_DAC), MP_ROM_INT(DAC_BUILT_IN) }, + { MP_ROM_QSTR(MP_QSTR_MODE_I2S), MP_ROM_INT(I2S) }, + { MP_ROM_QSTR(MP_QSTR_I2S_NUM_0), MP_ROM_INT(I2S_NUM_0) }, + { MP_ROM_QSTR(MP_QSTR_I2S_NUM_1), MP_ROM_INT(I2S_NUM_1) }, +}; + +STATIC MP_DEFINE_CONST_DICT(machine_i2s_locals_dict, machine_i2s_locals_dict_table); + +const mp_obj_type_t machine_i2s_type = { + { &mp_type_type }, + .name = MP_QSTR_I2S, + .make_new = machine_i2s_make_new, + .locals_dict = (mp_obj_dict_t*)&machine_i2s_locals_dict, +}; \ No newline at end of file diff --git a/MicroPython_BUILD/components/micropython/esp32/machine_i2s.h b/MicroPython_BUILD/components/micropython/esp32/machine_i2s.h new file mode 100644 index 0000000..80e0d21 --- /dev/null +++ b/MicroPython_BUILD/components/micropython/esp32/machine_i2s.h @@ -0,0 +1,40 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2017 Ayke van Laethem + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_ESP32_MACHINE_I2S_H +#define MICROPY_INCLUDED_ESP32_MACHINE_I2S_H + +#include "py/obj.h" +#include "driver/i2s.h" + +typedef struct { + mp_obj_base_t base; + i2s_port_t port; +} machine_i2s_obj_t; + +extern const mp_obj_type_t machine_i2s_type; + +#endif // MICROPY_INCLUDED_ESP32_MACHINE_I2S_H \ No newline at end of file diff --git a/MicroPython_BUILD/components/micropython/esp32/modules/chunk.py b/MicroPython_BUILD/components/micropython/esp32/modules/chunk.py new file mode 100644 index 0000000..ee3082f --- /dev/null +++ b/MicroPython_BUILD/components/micropython/esp32/modules/chunk.py @@ -0,0 +1,168 @@ +"""Simple class to read IFF chunks. + +An IFF chunk (used in formats such as AIFF, TIFF, RMFF (RealMedia File +Format)) has the following structure: + ++----------------+ +| ID (4 bytes) | ++----------------+ +| size (4 bytes) | ++----------------+ +| data | +| ... | ++----------------+ + +The ID is a 4-byte string which identifies the type of chunk. + +The size field (a 32-bit value, encoded using big-endian byte order) +gives the size of the whole chunk, including the 8-byte header. + +Usually an IFF-type file consists of one or more chunks. The proposed +usage of the Chunk class defined here is to instantiate an instance at +the start of each chunk and read from the instance until it reaches +the end, after which a new instance can be instantiated. At the end +of the file, creating a new instance will fail with a EOFError +exception. + +Usage: +while True: + try: + chunk = Chunk(file) + except EOFError: + break + chunktype = chunk.getname() + while True: + data = chunk.read(nbytes) + if not data: + pass + # do something with data + +The interface is file-like. The implemented methods are: +read, close, seek, tell, isatty. +Extra methods are: skip() (called by close, skips to the end of the chunk), +getname() (returns the name (ID) of the chunk) + +The __init__ method has one required argument, a file-like object +(including a chunk instance), and one optional argument, a flag which +specifies whether or not chunks are aligned on 2-byte boundaries. The +default is 1, i.e. aligned. +""" + +class Chunk: + def __init__(self, file, align=True, bigendian=True, inclheader=False): + import struct + self.closed = False + self.align = align # whether to align to word (2-byte) boundaries + if bigendian: + strflag = '>' + else: + strflag = '<' + self.file = file + self.chunkname = file.read(4) + if len(self.chunkname) < 4: + raise EOFError + try: + data = file.read(4) + self.chunksize = struct.unpack(strflag+'L', data)[0] + except:# struct.error: + raise EOFError + if inclheader: + self.chunksize = self.chunksize - 8 # subtract header + self.size_read = 0 + try: + self.offset = self.file.tell() + except: + self.seekable = False + else: + self.seekable = True + + def getname(self): + """Return the name (ID) of the current chunk.""" + return self.chunkname + + def getsize(self): + """Return the size of the current chunk.""" + return self.chunksize + + def close(self): + if not self.closed: + self.skip() + self.closed = True + + def isatty(self): + if self.closed: + raise ValueError("I/O operation on closed file") + return False + + def seek(self, pos, whence=0): + """Seek to specified position into the chunk. + Default position is 0 (start of chunk). + If the file is not seekable, this will result in an error. + """ + + if self.closed: + raise ValueError("I/O operation on closed file") + if not self.seekable: + raise OSError("cannot seek") + if whence == 1: + pos = pos + self.size_read + elif whence == 2: + pos = pos + self.chunksize + if pos < 0 or pos > self.chunksize: + raise RuntimeError + self.file.seek(self.offset + pos, 0) + self.size_read = pos + + def tell(self): + if self.closed: + raise ValueError("I/O operation on closed file") + return self.size_read + + def read(self, size=-1): + """Read at most size bytes from the chunk. + If size is omitted or negative, read until the end + of the chunk. + """ + + if self.closed: + raise ValueError("I/O operation on closed file") + if self.size_read >= self.chunksize: + return '' + if size < 0: + size = self.chunksize - self.size_read + if size > self.chunksize - self.size_read: + size = self.chunksize - self.size_read + data = self.file.read(size) + self.size_read = self.size_read + len(data) + if self.size_read == self.chunksize and \ + self.align and \ + (self.chunksize & 1): + dummy = self.file.read(1) + self.size_read = self.size_read + len(dummy) + return data + + def skip(self): + """Skip the rest of the chunk. + If you are not interested in the contents of the chunk, + this method should be called so that the file points to + the start of the next chunk. + """ + + if self.closed: + raise ValueError("I/O operation on closed file") + if self.seekable: + try: + n = self.chunksize - self.size_read + # maybe fix alignment + if self.align and (self.chunksize & 1): + n = n + 1 + self.file.seek(n, 1) + self.size_read = self.size_read + n + return + except OSError: + pass + while self.size_read < self.chunksize: + n = min(8192, self.chunksize - self.size_read) + dummy = self.read(n) + if not dummy: + raise EOFError \ No newline at end of file diff --git a/MicroPython_BUILD/components/micropython/esp32/modules/m5stack.py b/MicroPython_BUILD/components/micropython/esp32/modules/m5stack.py index ac35cbf..d82cc51 100644 --- a/MicroPython_BUILD/components/micropython/esp32/modules/m5stack.py +++ b/MicroPython_BUILD/components/micropython/esp32/modules/m5stack.py @@ -6,7 +6,7 @@ from utime import ticks_ms import display as lcd import utils -VERSION = "v0.3.5" +VERSION = "v0.3.6" _BUTTON_A_PIN = const(39) _BUTTON_B_PIN = const(38) @@ -147,6 +147,10 @@ def fimage(x, y, file): lcd.image(x, y, file, 0, lcd.JPG) +def delay(ms): + time.sleep_ms(ms) + + # -------- M5Stack --------- # Node ID @@ -175,4 +179,4 @@ buttonC = Button(_BUTTON_C_PIN) # SPEAKER -speaker = Speaker() +# speaker = Speaker() diff --git a/MicroPython_BUILD/components/micropython/esp32/modules/wave.py b/MicroPython_BUILD/components/micropython/esp32/modules/wave.py new file mode 100644 index 0000000..efedb25 --- /dev/null +++ b/MicroPython_BUILD/components/micropython/esp32/modules/wave.py @@ -0,0 +1,503 @@ +"""Stuff to parse WAVE files. + +Usage. + +Reading WAVE files: + f = wave.open(file, 'r') +where file is either the name of a file or an open file pointer. +The open file pointer must have methods read(), seek(), and close(). +When the setpos() and rewind() methods are not used, the seek() +method is not necessary. + +This returns an instance of a class with the following public methods: + getnchannels() -- returns number of audio channels (1 for + mono, 2 for stereo) + getsampwidth() -- returns sample width in bytes + getframerate() -- returns sampling frequency + getnframes() -- returns number of audio frames + getcomptype() -- returns compression type ('NONE' for linear samples) + getcompname() -- returns human-readable version of + compression type ('not compressed' linear samples) + getparams() -- returns a namedtuple consisting of all of the + above in the above order + getmarkers() -- returns None (for compatibility with the + aifc module) + getmark(id) -- raises an error since the mark does not + exist (for compatibility with the aifc module) + readframes(n) -- returns at most n frames of audio + rewind() -- rewind to the beginning of the audio stream + setpos(pos) -- seek to the specified position + tell() -- return the current position + close() -- close the instance (make it unusable) +The position returned by tell() and the position given to setpos() +are compatible and have nothing to do with the actual position in the +file. +The close() method is called automatically when the class instance +is destroyed. + +Writing WAVE files: + f = wave.open(file, 'w') +where file is either the name of a file or an open file pointer. +The open file pointer must have methods write(), tell(), seek(), and +close(). + +This returns an instance of a class with the following public methods: + setnchannels(n) -- set the number of channels + setsampwidth(n) -- set the sample width + setframerate(n) -- set the frame rate + setnframes(n) -- set the number of frames + setcomptype(type, name) + -- set the compression type and the + human-readable compression type + setparams(tuple) + -- set all parameters at once + tell() -- return current position in output file + writeframesraw(data) + -- write audio frames without pathing up the + file header + writeframes(data) + -- write audio frames and patch up the file header + close() -- patch up the file header and close the + output file +You should set the parameters before the first writeframesraw or +writeframes. The total number of frames does not need to be set, +but when it is set to the correct value, the header does not have to +be patched up. +It is best to first set all parameters, perhaps possibly the +compression type, and then write audio frames using writeframesraw. +When all frames have been written, either call writeframes('') or +close() to patch up the sizes in the header. +The close() method is called automatically when the class instance +is destroyed. +""" + +import builtins + +__all__ = ["open", "openfp", "Error"] + +class Error(Exception): + pass + +WAVE_FORMAT_PCM = 0x0001 + +_array_fmts = None, 'b', 'h', None, 'i' + +#import audioop +import struct +import sys +from chunk import Chunk +from collections import namedtuple + +_wave_params = namedtuple('_wave_params', + 'nchannels sampwidth framerate nframes comptype compname') + +class Wave_read: + """Variables used in this class: + + These variables are available to the user though appropriate + methods of this class: + _file -- the open file with methods read(), close(), and seek() + set through the __init__() method + _nchannels -- the number of audio channels + available through the getnchannels() method + _nframes -- the number of audio frames + available through the getnframes() method + _sampwidth -- the number of bytes per audio sample + available through the getsampwidth() method + _framerate -- the sampling frequency + available through the getframerate() method + _comptype -- the AIFF-C compression type ('NONE' if AIFF) + available through the getcomptype() method + _compname -- the human-readable AIFF-C compression type + available through the getcomptype() method + _soundpos -- the position in the audio stream + available through the tell() method, set through the + setpos() method + + These variables are used internally only: + _fmt_chunk_read -- 1 iff the FMT chunk has been read + _data_seek_needed -- 1 iff positioned correctly in audio + file for readframes() + _data_chunk -- instantiation of a chunk class for the DATA chunk + _framesize -- size of one frame in the file + """ + + def initfp(self, file): + self._convert = None + self._soundpos = 0 + self._file = Chunk(file, bigendian = 0) + if self._file.getname() != b'RIFF': + raise Error('file does not start with RIFF id') + if self._file.read(4) != b'WAVE': + raise Error('not a WAVE file') + self._fmt_chunk_read = 0 + self._data_chunk = None + while 1: + self._data_seek_needed = 1 + try: + chunk = Chunk(self._file, bigendian = 0) + except EOFError: + break + chunkname = chunk.getname() + if chunkname == b'fmt ': + self._read_fmt_chunk(chunk) + self._fmt_chunk_read = 1 + elif chunkname == b'data': + if not self._fmt_chunk_read: + raise Error('data chunk before fmt chunk') + self._data_chunk = chunk + self._nframes = chunk.chunksize // self._framesize + self._data_seek_needed = 0 + break + chunk.skip() + if not self._fmt_chunk_read or not self._data_chunk: + raise Error('fmt chunk and/or data chunk missing') + + def __init__(self, f): + self._i_opened_the_file = None + if isinstance(f, str): + f = builtins.open(f, 'rb') + self._i_opened_the_file = f + # else, assume it is an open file object already + try: + self.initfp(f) + except: + if self._i_opened_the_file: + f.close() + raise + + def __del__(self): + self.close() + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + # + # User visible methods. + # + def getfp(self): + return self._file + + def rewind(self): + self._data_seek_needed = 1 + self._soundpos = 0 + + def close(self): + if self._i_opened_the_file: + self._i_opened_the_file.close() + self._i_opened_the_file = None + self._file = None + + def tell(self): + return self._soundpos + + def getnchannels(self): + return self._nchannels + + def getnframes(self): + return self._nframes + + def getsampwidth(self): + return self._sampwidth + + def getframerate(self): + return self._framerate + + def getcomptype(self): + return self._comptype + + def getcompname(self): + return self._compname + + def getparams(self): + return _wave_params(self.getnchannels(), self.getsampwidth(), + self.getframerate(), self.getnframes(), + self.getcomptype(), self.getcompname()) + + def getmarkers(self): + return None + + def getmark(self, id): + raise Error('no marks') + + def setpos(self, pos): + if pos < 0 or pos > self._nframes: + raise Error('position not in range') + self._soundpos = pos + self._data_seek_needed = 1 + + def readframes(self, nframes): + if self._data_seek_needed: + self._data_chunk.seek(0, 0) + pos = self._soundpos * self._framesize + if pos: + self._data_chunk.seek(pos, 0) + self._data_seek_needed = 0 + if nframes == 0: + return b'' + data = self._data_chunk.read(nframes * self._framesize) + if self._sampwidth != 1 and sys.byteorder == 'big': + data = audioop.byteswap(data, self._sampwidth) + if self._convert and data: + data = self._convert(data) + self._soundpos = self._soundpos + len(data) // (self._nchannels * self._sampwidth) + return data + + # + # Internal methods. + # + + def _read_fmt_chunk(self, chunk): + wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack(' 4: + raise Error('bad sample width') + self._sampwidth = sampwidth + + def getsampwidth(self): + if not self._sampwidth: + raise Error('sample width not set') + return self._sampwidth + + def setframerate(self, framerate): + if self._datawritten: + raise Error('cannot change parameters after starting to write') + if framerate <= 0: + raise Error('bad frame rate') + self._framerate = int(round(framerate)) + + def getframerate(self): + if not self._framerate: + raise Error('frame rate not set') + return self._framerate + + def setnframes(self, nframes): + if self._datawritten: + raise Error('cannot change parameters after starting to write') + self._nframes = nframes + + def getnframes(self): + return self._nframeswritten + + def setcomptype(self, comptype, compname): + if self._datawritten: + raise Error('cannot change parameters after starting to write') + if comptype not in ('NONE',): + raise Error('unsupported compression type') + self._comptype = comptype + self._compname = compname + + def getcomptype(self): + return self._comptype + + def getcompname(self): + return self._compname + + def setparams(self, params): + nchannels, sampwidth, framerate, nframes, comptype, compname = params + if self._datawritten: + raise Error('cannot change parameters after starting to write') + self.setnchannels(nchannels) + self.setsampwidth(sampwidth) + self.setframerate(framerate) + self.setnframes(nframes) + self.setcomptype(comptype, compname) + + def getparams(self): + if not self._nchannels or not self._sampwidth or not self._framerate: + raise Error('not all parameters set') + return _wave_params(self._nchannels, self._sampwidth, self._framerate, + self._nframes, self._comptype, self._compname) + + def setmark(self, id, pos, name): + raise Error('setmark() not supported') + + def getmark(self, id): + raise Error('no marks') + + def getmarkers(self): + return None + + def tell(self): + return self._nframeswritten + + def writeframesraw(self, data): + if not isinstance(data, (bytes, bytearray)): + data = memoryview(data).cast('B') + self._ensure_header_written(len(data)) + nframes = len(data) // (self._sampwidth * self._nchannels) + if self._convert: + data = self._convert(data) + if self._sampwidth != 1 and sys.byteorder == 'big': + data = audioop.byteswap(data, self._sampwidth) + self._file.write(data) + self._datawritten += len(data) + self._nframeswritten = self._nframeswritten + nframes + + def writeframes(self, data): + self.writeframesraw(data) + if self._datalength != self._datawritten: + self._patchheader() + + def close(self): + if self._file: + try: + self._ensure_header_written(0) + if self._datalength != self._datawritten: + self._patchheader() + self._file.flush() + finally: + self._file = None + if self._i_opened_the_file: + self._i_opened_the_file.close() + self._i_opened_the_file = None + + # + # Internal methods. + # + + def _ensure_header_written(self, datasize): + if not self._headerwritten: + if not self._nchannels: + raise Error('# channels not specified') + if not self._sampwidth: + raise Error('sample width not specified') + if not self._framerate: + raise Error('sampling rate not specified') + self._write_header(datasize) + + def _write_header(self, initlength): + assert not self._headerwritten + self._file.write(b'RIFF') + if not self._nframes: + self._nframes = initlength // (self._nchannels * self._sampwidth) + self._datalength = self._nframes * self._nchannels * self._sampwidth + try: + self._form_length_pos = self._file.tell() + except (AttributeError, OSError): + self._form_length_pos = None + self._file.write(struct.pack('