Source

pygame / src / sdlmixer / numericsndarraymod.c

Full commit
/*
  pygame - Python Game Library
  Copyright (C) 2000-2001  Pete Shinners

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Library General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Library General Public License for more details.

  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/
#define PYGAME_SOUNDARRAY_INTERNAL

#include "pgmixer.h"
#include "numeric_arrayobject.h"

#ifndef IS_PYTHON_3

static PyObject* _sndarray_samples (PyObject* self, PyObject* arg);
static PyObject* _sndarray_array (PyObject* self, PyObject* arg);
static PyObject* _sndarray_make_sound (PyObject* self, PyObject* arg);

static PyMethodDef _sndarray_methods[] =
{
    { "samples", _sndarray_samples, METH_VARARGS, "" },
    { "make_array", _sndarray_array, METH_VARARGS, "" },
    { "make_sound", _sndarray_make_sound, METH_VARARGS, "" },
    { NULL, NULL, 0, NULL},
};

static PyObject*
_sndarray_samples (PyObject* self, PyObject* arg)
{
    int dim[2], numdims, type, formatbytes;
    PyObject *array, *chunkobj;
    Mix_Chunk* chunk;
    Uint16 format;
    int numchannels;

    if (!PyArg_ParseTuple (arg, "O", &chunkobj))
        return NULL;
    if (!PyChunk_Check (chunkobj))
    {
        PyErr_SetString (PyExc_TypeError, "chunk must be a Chunk");
        return NULL;
    }
    chunk = ((PyChunk*)chunkobj)->chunk;

    if (!Mix_QuerySpec (NULL, &format, &numchannels))
    {
        PyErr_SetString (PyExc_PyGameError, "mixer not open");
        return NULL;
    }

    formatbytes = (abs (format) & 0xff) / 8;
    switch (format)
    {
    case AUDIO_S8:
        type = PyArray_CHAR;
        break;
    case AUDIO_U8:
        type = PyArray_UBYTE;
        break;
    case AUDIO_S16SYS:
        type = PyArray_SHORT;
        break;
    case AUDIO_U16SYS:
        type = PyArray_USHORT;
        break;
    default:
        PyErr_SetString (PyExc_TypeError, "unpresentable audio format");
        return NULL;
    }

    numdims = (numchannels > 1) ? 2 : 1;
    dim[0] = chunk->alen / (numchannels*formatbytes);
    dim[1] = numchannels;
    
    array = PyArray_FromDimsAndData (numdims, dim, type, (char*)chunk->abuf);
    if (array)
    {
        Py_INCREF (chunkobj);
        ((PyArrayObject*) array)->base = chunkobj;
        ((PyArrayObject*) array)->flags |= SAVESPACE;
    }
    return array;
}

PyObject*
_sndarray_array (PyObject* self, PyObject* arg)
{
    PyObject *array, *arraycopy=NULL;
    
    /* we'll let numeric do the copying for us */
    array = _sndarray_samples (self, arg);
    if (array)
    {
        arraycopy = PyArray_Copy ((PyArrayObject*) array);
        Py_DECREF (array);
    }
    return arraycopy;
}

PyObject*
_sndarray_make_sound (PyObject* self, PyObject* arg)
{
    PyObject *arrayobj, *chunkobj;
    PyArrayObject *array;
    Mix_Chunk *chunk;
    Uint16 format;
    int numchannels, mixerbytes;
    int loop1, loop2, step1, step2, length, length2=0;
    Uint8 *src, *dst;

    if (!PyArg_ParseTuple (arg, "O", &arrayobj))
	return NULL;
    if (!PyArray_Check (arrayobj))
    {
        PyErr_SetString (PyExc_TypeError, "array must be a numeric array");
        return NULL;
    }

    array = (PyArrayObject*) arrayobj;
    
    if (!Mix_QuerySpec (NULL, &format, &numchannels))
    {
        PyErr_SetString (PyExc_PyGameError, "mixer not open");
        return NULL;
    }

    if (array->descr->type_num > PyArray_LONG)
    {
        PyErr_SetString (PyExc_ValueError, "invalid array datatype for sound");
        return NULL;
    }

    if (format==AUDIO_S8 || format==AUDIO_U8)
        mixerbytes = 1;
    else
        mixerbytes = 2;
    
    /*test array dimensions*/
    if (numchannels == 1)
    {
        if (array->nd != 1)
        {
            PyErr_SetString (PyExc_ValueError,
                "array must be 1-dimensional for mono mixer");
            return NULL;
        }
    }
    else
    {
        if (array->nd != 2)
        {
            PyErr_SetString (PyExc_ValueError,
                "array must be 2-dimensional for stereo mixer");
            return NULL;
        }

        if (array->dimensions[1] != numchannels)
        {
            PyErr_SetString (PyExc_ValueError,
                "array depth must match number of mixer channels");
            return NULL;
        }
    }

    length = array->dimensions[0];
    step1 = array->strides[0];
    if (array->nd == 2)
    {
        length2 = array->dimensions[1];
	step2 = array->strides[1];
    }
    else 
    {
        length2 = 1;
        /*since length2 == 1, this won't be used for looping*/
	step2 = mixerbytes; 
    }

    /*create chunk, we are screwed if SDL_mixer ever does more than
     * malloc/free*/
    chunk = (Mix_Chunk *) malloc (sizeof (Mix_Chunk));
    if (!chunk)
    {
        PyErr_SetString (PyExc_MemoryError, "Cannot allocate chunk\n");
        return NULL;
    }

    /*let's hope Mix_Chunk never changes also*/
    chunk->alen = length * numchannels * mixerbytes;
    chunk->abuf = (Uint8*) malloc (chunk->alen);
    chunk->allocated = 1;
    chunk->volume = 128;

    Py_BEGIN_ALLOW_THREADS;
    if (step1 == mixerbytes * numchannels && step2 == mixerbytes)
    {
        /*OPTIMIZATION: in these cases, we don't need to loop through
         *the samples individually, because the bytes are already layed
         *out correctly*/
        memcpy (chunk->abuf, array->data, chunk->alen);
    }
    else
    {
        dst = (Uint8*) chunk->abuf;
        if (mixerbytes == 1)
        {
            for (loop1 = 0; loop1 < length; loop1++)
            {
                src = (Uint8*) array->data + loop1*step1;
                switch (array->descr->elsize)
                {
                case 1:
                    for (loop2=0; loop2<length2; loop2++, dst+=1, src+=step2)
                        *(Uint8*)dst = (Uint8)*((Uint8*)src);
                    break;
                case 2:
                    for (loop2=0; loop2<length2; loop2++, dst+=1, src+=step2)
                        *(Uint8*)dst = (Uint8)*((Uint16*)src);
                    break;
                case 4:
                    for (loop2=0; loop2<length2; loop2++, dst+=1, src+=step2)
                        *(Uint8*)dst = (Uint8)*((Uint32*)src);
                    break;
                }
            }
        }
        else
        {
            for (loop1 = 0; loop1 < length; loop1++)
            {
                src = (Uint8*) array->data + loop1*step1;
                switch (array->descr->elsize)
                {
                case 1:
                    for (loop2=0; loop2<length2; loop2++, dst+=2, src+=step2)
                        *(Uint16*)dst = (Uint16)(*((Uint8*)src)<<8);
                    break;
                case 2:
                    for (loop2=0; loop2<length2; loop2++, dst+=2, src+=step2)
                        *(Uint16*)dst = (Uint16)*((Uint16*)src);
                    break;
                case 4:
                    for (loop2=0; loop2<length2; loop2++, dst+=2, src+=step2)
                        *(Uint16*)dst = (Uint16)*((Uint32*)src);
                    break;
                }
            }
        }
    }
    Py_END_ALLOW_THREADS;
    
    chunkobj = PyChunk_NewFromMixerChunk (chunk);
    if (!chunkobj)
    {
        Mix_FreeChunk (chunk);
        return NULL;
    }
    
    return chunkobj;
}

PyMODINIT_FUNC initnumericsndarray (void)
{
    PyObject *mod;
    mod = Py_InitModule3 ("numericsndarray", _sndarray_methods, "");
        
    /*imported needed apis*/
    import_pygame2_sdlmixer_base ();
    import_array ();
}

#endif /* !IS_PYTHON_3 */