PyObject* pyGetMetadata(PyObject* self, PyObject* args) { PyObject* songObj = NULL; if(!PyArg_ParseTuple(args, "O:getMetadata", &songObj)) return NULL; PyObject* returnObj = NULL; PlayerObject* player = (PlayerObject*) pyCreatePlayer(NULL); if(!player) goto final; player->nextSongOnEof = 0; player->skipPyExceptions = 0; Py_INCREF(songObj); player->curSong = songObj; player->openInStream(); if(PyErr_Occurred()) goto final; returnObj = player->curSongMetadata(); final:
PyObject * pyCalcReplayGain(PyObject* self, PyObject* args, PyObject* kws) { PyObject* songObj = NULL; static const char *kwlist[] = { "song", NULL}; if(!PyArg_ParseTupleAndKeywords( args, kws, "O:calcReplayGain", (char**)kwlist, &songObj )) return NULL; PyObject* returnObj = NULL; PlayerObject* player = NULL; ReplayGainBuffer* buffer = NULL; unsigned long totalFrameCount = 0; size_t samplePos = 0; size_t windowCount = 0; player = (PlayerObject*) pyCreatePlayer(NULL); if(!player) goto final; player->lock.enabled = false; player->setAudioTgt(SAMPLERATE, NUMCHANNELS); player->nextSongOnEof = 0; player->skipPyExceptions = 0; player->playing = true; // otherwise audio_decode_frame() wont read player->volume = 1; player->volumeSmoothClip.setX(1, 1); // avoid volume adjustments assert(!player->volumeAdjustNeeded()); Py_INCREF(songObj); player->curSong = songObj; if(PyObject_HasAttrString(songObj, "gain")) printf("pyCalcReplayGain: warning: song has gain already - this will lead to wrong gain calculation\n"); if(!player->openInStream()) goto final; if(PyErr_Occurred()) goto final; if(!player->isInStreamOpened()) goto final; buffer = (ReplayGainBuffer*)malloc(sizeof(ReplayGainBuffer)); memset(buffer, 0, sizeof(ReplayGainBuffer)); while (player->processInStream()) { if(PyErr_Occurred()) goto final; for(auto& it : player->inStreamBuffer()->chunks) { totalFrameCount += it.size() / NUMCHANNELS / 2 /* S16 */; short channel = 0; for(size_t i = 0; i < it.size() / 2; ++i) { int16_t* sampleAddr = (int16_t*) it.pt() + i; int16_t sample = *sampleAddr; // TODO: endian swap? // It is by purpose that we don't normalize to [-1,1] but stay in the range [-0x8000,0x7fff]. // That is because it was originially based on CD data, which is 16-bit signed integers. float sampleFloat = sample; buffer->channels[channel].stages[0].data[samplePos + MAX_FILTER_ORDER] = sampleFloat; ++channel; if(channel >= NUMCHANNELS) { channel = 0; ++samplePos; if(samplePos >= MAX_SAMPLES_PER_WINDOW) { // buffer is full. i.e. we have a full window. handle it. replayGainHandleWindow(buffer); ++windowCount; // move on now. for(int chan = 0; chan < NUMCHANNELS; ++chan) for(int stage = 0; stage < NUM_REPLAYGAIN_STAGES; ++stage) memcpy( buffer->channels[chan].stages[stage].data, buffer->channels[chan].stages[stage].data + MAX_SAMPLES_PER_WINDOW, MAX_FILTER_ORDER * sizeof(buffer->channels[0].stages[0].data[0])); samplePos = 0; } } } } player->inStreamBuffer()->clear(); } { double songDuration = (double)totalFrameCount / SAMPLERATE; float gain = 0; int64_t upperLoudness = (int64_t) ceil(windowCount * (1.0 - REPLAYGAIN_LOUD_PERC)); for(int i = sizeof(buffer->loudnessTable)/sizeof(buffer->loudnessTable[0]) - 1; i >= 0; --i) { upperLoudness -= buffer->loudnessTable[i]; if(upperLoudness <= 0) { gain = RG_PINK_REF - (float)i / RG_STEPS_per_dB; break; } } returnObj = PyTuple_New(2); PyTuple_SetItem(returnObj, 0, PyFloat_FromDouble(songDuration)); PyTuple_SetItem(returnObj, 1, PyFloat_FromDouble(gain)); } final: if(buffer) free(buffer);