{"action":"created","comment":{"url":"https://api.github.com/repos/mixxxdj/mixxx/pulls/comments/23753122","id":23753122,"diff_hunk":"@@ -0,0 +1,375 @@\n+#include \"sources/audiosourceflac.h\"\n+\n+#include \"sampleutil.h\"\n+#include \"util/math.h\"\n+\n+#include <QtDebug>\n+\n+namespace Mixxx {\n+\n+namespace {\n+// begin callbacks (have to be regular functions because normal libFLAC isn't C++-aware)\n+\n+FLAC__StreamDecoderReadStatus FLAC_read_cb(const FLAC__StreamDecoder*,\n+        FLAC__byte buffer[], size_t *bytes, void *client_data) {\n+    return static_cast<AudioSourceFLAC*>(client_data)->flacRead(buffer, bytes);\n+}\n+\n+FLAC__StreamDecoderSeekStatus FLAC_seek_cb(const FLAC__StreamDecoder*,\n+        FLAC__uint64 absolute_byte_offset, void *client_data) {\n+    return static_cast<AudioSourceFLAC*>(client_data)->flacSeek(\n+            absolute_byte_offset);\n+}\n+\n+FLAC__StreamDecoderTellStatus FLAC_tell_cb(const FLAC__StreamDecoder*,\n+        FLAC__uint64 *absolute_byte_offset, void *client_data) {\n+    return static_cast<AudioSourceFLAC*>(client_data)->flacTell(\n+            absolute_byte_offset);\n+}\n+\n+FLAC__StreamDecoderLengthStatus FLAC_length_cb(const FLAC__StreamDecoder*,\n+        FLAC__uint64 *stream_length, void *client_data) {\n+    return static_cast<AudioSourceFLAC*>(client_data)->flacLength(stream_length);\n+}\n+\n+FLAC__bool FLAC_eof_cb(const FLAC__StreamDecoder*, void *client_data) {\n+    return static_cast<AudioSourceFLAC*>(client_data)->flacEOF();\n+}\n+\n+FLAC__StreamDecoderWriteStatus FLAC_write_cb(const FLAC__StreamDecoder*,\n+        const FLAC__Frame *frame, const FLAC__int32 * const buffer[],\n+        void *client_data) {\n+    return static_cast<AudioSourceFLAC*>(client_data)->flacWrite(frame, buffer);\n+}\n+\n+void FLAC_metadata_cb(const FLAC__StreamDecoder*,\n+        const FLAC__StreamMetadata *metadata, void *client_data) {\n+    static_cast<AudioSourceFLAC*>(client_data)->flacMetadata(metadata);\n+}\n+\n+void FLAC_error_cb(const FLAC__StreamDecoder*,\n+        FLAC__StreamDecoderErrorStatus status, void *client_data) {\n+    static_cast<AudioSourceFLAC*>(client_data)->flacError(status);\n+}\n+\n+// end callbacks\n+}\n+\n+AudioSourceFLAC::AudioSourceFLAC(QUrl url)\n+        : AudioSource(url),\n+          m_file(getLocalFileName()),\n+          m_decoder(NULL),\n+          m_minBlocksize(0),\n+          m_maxBlocksize(0),\n+          m_minFramesize(0),\n+          m_maxFramesize(0),\n+          m_sampleScale(kSampleValueZero),\n+          m_decodeSampleBufferReadOffset(0),\n+          m_decodeSampleBufferWriteOffset(0),\n+          m_curFrameIndex(kFrameIndexMin) {\n+}\n+\n+AudioSourceFLAC::~AudioSourceFLAC() {\n+    preDestroy();\n+}\n+\n+AudioSourcePointer AudioSourceFLAC::create(QUrl url) {\n+    return onCreate(new AudioSourceFLAC(url));\n+}\n+\n+Result AudioSourceFLAC::postConstruct() {\n+    if (!m_file.open(QIODevice::ReadOnly)) {\n+        qWarning() << \"SSFLAC: Could not read file!\";\n+        return ERR;\n+    }\n+\n+    m_decoder = FLAC__stream_decoder_new();\n+    if (m_decoder == NULL) {\n+        qWarning() << \"SSFLAC: decoder allocation failed!\";\n+        return ERR;\n+    }\n+    FLAC__stream_decoder_set_md5_checking(m_decoder, FALSE);\n+    const FLAC__StreamDecoderInitStatus initStatus(\n+            FLAC__stream_decoder_init_stream(m_decoder, FLAC_read_cb,\n+                    FLAC_seek_cb, FLAC_tell_cb, FLAC_length_cb, FLAC_eof_cb,\n+                    FLAC_write_cb, FLAC_metadata_cb, FLAC_error_cb, this));\n+    if (initStatus != FLAC__STREAM_DECODER_INIT_STATUS_OK) {\n+        qWarning() << \"SSFLAC: decoder init failed:\" << initStatus;\n+        return ERR;\n+    }\n+    if (!FLAC__stream_decoder_process_until_end_of_metadata(m_decoder)) {\n+        qWarning() << \"SSFLAC: process to end of meta failed:\"\n+                << FLAC__stream_decoder_get_state(m_decoder);\n+        return ERR;\n+    }\n+\n+    return OK;\n+}\n+\n+void AudioSourceFLAC::preDestroy() {\n+    if (m_decoder) {\n+        FLAC__stream_decoder_finish(m_decoder);\n+        FLAC__stream_decoder_delete(m_decoder); // frees memory\n+        m_decoder = NULL;\n+    }\n+    m_file.close();\n+}\n+\n+Mixxx::AudioSource::diff_type AudioSourceFLAC::seekSampleFrame(\n+        diff_type frameIndex) {\n+    DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex));\n+    DEBUG_ASSERT(isValidFrameIndex(frameIndex));\n+\n+    // clear decode buffer before seeking\n+    m_decodeSampleBufferReadOffset = 0;\n+    m_decodeSampleBufferWriteOffset = 0;\n+    if (!FLAC__stream_decoder_seek_absolute(m_decoder, frameIndex)) {\n+        qWarning() << \"SSFLAC: Seeking error at file\" << m_file.fileName();\n+    }\n+    if ((FLAC__STREAM_DECODER_SEEK_ERROR == FLAC__stream_decoder_get_state(m_decoder)) &&\n+        !FLAC__stream_decoder_flush(m_decoder)) {\n+        qWarning() << \"SSFLAC: Failed to flush the decoder's input buffer after seeking\" << m_file.fileName();\n+    }\n+    m_curFrameIndex = frameIndex;\n+\n+    DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex));\n+    return m_curFrameIndex;\n+}\n+\n+Mixxx::AudioSource::size_type AudioSourceFLAC::readSampleFrames(\n+        size_type numberOfFrames, sample_type* sampleBuffer) {\n+    return readSampleFrames(numberOfFrames, sampleBuffer,\n+            frames2samples(numberOfFrames), false);\n+}\n+\n+Mixxx::AudioSource::size_type AudioSourceFLAC::readSampleFramesStereo(\n+        size_type numberOfFrames, sample_type* sampleBuffer,\n+        size_type sampleBufferSize) {\n+    return readSampleFrames(numberOfFrames, sampleBuffer, sampleBufferSize,\n+            true);\n+}\n+\n+Mixxx::AudioSource::size_type AudioSourceFLAC::readSampleFrames(\n+        size_type numberOfFrames, sample_type* sampleBuffer,\n+        size_type sampleBufferSize, bool readStereoSamples) {\n+    DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex));\n+    DEBUG_ASSERT(getSampleBufferSize(numberOfFrames, readStereoSamples) <= sampleBufferSize);\n+\n+    const size_type numberOfFramesTotal = numberOfFrames;\n+\n+    sample_type* outBuffer = sampleBuffer;\n+    size_type numberOfFramesRemaining = numberOfFramesTotal;\n+    while (0 < numberOfFramesRemaining) {\n+        DEBUG_ASSERT(\n+                m_decodeSampleBufferReadOffset <= m_decodeSampleBufferWriteOffset);\n+        // if our buffer from libflac is empty (either because we explicitly cleared\n+        // it or because we've simply used all the samples), ask for a new buffer\n+        if (m_decodeSampleBufferReadOffset >= m_decodeSampleBufferWriteOffset) {\n+            if (FLAC__stream_decoder_process_single(m_decoder)) {\n+                if (m_decodeSampleBufferReadOffset\n+                        >= m_decodeSampleBufferWriteOffset) {\n+                    // EOF\n+                    break;\n+                }\n+            } else {\n+                qWarning() << \"SSFLAC: decoder_process_single returned false (\"\n+                        << m_file.fileName() << \")\";\n+                break;\n+            }\n+        }\n+        DEBUG_ASSERT(\n+                m_decodeSampleBufferReadOffset <= m_decodeSampleBufferWriteOffset);\n+        const size_type decodeBufferSamples = m_decodeSampleBufferWriteOffset\n+                - m_decodeSampleBufferReadOffset;\n+        const size_type decodeBufferFrames = samples2frames(\n+                decodeBufferSamples);\n+        const size_type framesToCopy =\n+                math_min(decodeBufferFrames, numberOfFramesRemaining);\n+        const size_type samplesToCopy = frames2samples(framesToCopy);\n+        if (readStereoSamples && !isChannelCountStereo()) {\n+            if (isChannelCountMono()) {\n+                SampleUtil::copyMonoToDualMono(outBuffer,\n+                        &m_decodeSampleBuffer[m_decodeSampleBufferReadOffset],\n+                        framesToCopy);\n+            } else {\n+                SampleUtil::copyMultiToStereo(outBuffer,\n+                        &m_decodeSampleBuffer[m_decodeSampleBufferReadOffset],\n+                        framesToCopy, getChannelCount());\n+            }\n+            outBuffer += framesToCopy * 2; // copied 2 samples per frame\n+        } else {\n+            SampleUtil::copy(outBuffer,\n+                    &m_decodeSampleBuffer[m_decodeSampleBufferReadOffset],\n+                    samplesToCopy);\n+            outBuffer += samplesToCopy;\n+        }\n+        m_decodeSampleBufferReadOffset += samplesToCopy;\n+        m_curFrameIndex += framesToCopy;\n+        numberOfFramesRemaining -= framesToCopy;\n+        DEBUG_ASSERT(\n+                m_decodeSampleBufferReadOffset <= m_decodeSampleBufferWriteOffset);\n+    }\n+\n+    DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex));\n+    DEBUG_ASSERT(numberOfFramesTotal >= numberOfFramesRemaining);\n+    return numberOfFramesTotal - numberOfFramesRemaining;\n+}\n+\n+// flac callback methods\n+FLAC__StreamDecoderReadStatus AudioSourceFLAC::flacRead(FLAC__byte buffer[],\n+        size_t *bytes) {\n+    *bytes = m_file.read((char*) buffer, *bytes);\n+    if (*bytes > 0) {\n+        return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;\n+    } else if (*bytes == 0) {\n+        return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;\n+    } else {\n+        return FLAC__STREAM_DECODER_READ_STATUS_ABORT;\n+    }\n+}\n+\n+FLAC__StreamDecoderSeekStatus AudioSourceFLAC::flacSeek(FLAC__uint64 offset) {\n+    if (m_file.seek(offset)) {\n+        return FLAC__STREAM_DECODER_SEEK_STATUS_OK;\n+    } else {\n+        qWarning() << \"SSFLAC: An unrecoverable error occurred (\"\n+                << m_file.fileName() << \")\";\n+        return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;\n+    }\n+}\n+\n+FLAC__StreamDecoderTellStatus AudioSourceFLAC::flacTell(FLAC__uint64 *offset) {\n+    if (m_file.isSequential()) {\n+        return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED;\n+    }\n+    *offset = m_file.pos();\n+    return FLAC__STREAM_DECODER_TELL_STATUS_OK;\n+}\n+\n+FLAC__StreamDecoderLengthStatus AudioSourceFLAC::flacLength(\n+        FLAC__uint64 *length) {\n+    if (m_file.isSequential()) {\n+        return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED;\n+    }\n+    *length = m_file.size();\n+    return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;\n+}\n+\n+FLAC__bool AudioSourceFLAC::flacEOF() {\n+    if (m_file.isSequential()) {\n+        return false;\n+    }\n+    return m_file.atEnd();\n+}\n+\n+FLAC__StreamDecoderWriteStatus AudioSourceFLAC::flacWrite(\n+        const FLAC__Frame *frame, const FLAC__int32 * const buffer[]) {\n+    // decode buffer must be empty before decoding the next frame\n+    DEBUG_ASSERT(m_decodeSampleBufferReadOffset >= m_decodeSampleBufferWriteOffset);\n+    // reset decode buffer\n+    m_decodeSampleBufferReadOffset = 0;\n+    m_decodeSampleBufferWriteOffset = 0;\n+    if (getChannelCount() != frame->header.channels) {\n+        qWarning() << \"Corrupt or unsupported FLAC file:\"\n+                << \"Invalid number of channels in FLAC frame header\"\n+                << frame->header.channels << \"<>\" << getChannelCount();\n+        return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;\n+    }\n+    if (getFrameRate() != frame->header.sample_rate) {\n+        qWarning() << \"Corrupt or unsupported FLAC file:\"\n+                << \"Invalid sample rate in FLAC frame header\"\n+                << frame->header.sample_rate << \"<>\" << getFrameRate();\n+        return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;\n+    }\n+    const SampleBuffer::size_type maxBlocksize = samples2frames(\n+            m_decodeSampleBuffer.size());\n+    if (maxBlocksize < frame->header.blocksize) {\n+        qWarning() << \"Corrupt or unsupported FLAC file:\"\n+                << \"Block size in FLAC frame header exceeds the maximum block size\"\n+                << frame->header.blocksize << \">\" << maxBlocksize;\n+    }\n+    switch (getChannelCount()) {\n+    case 1: {\n+        // optimized code for 1 channel (mono)\n+        DEBUG_ASSERT(1 <= frame->header.channels);\n+        for (unsigned i = 0; i < frame->header.blocksize; ++i) {\n+            m_decodeSampleBuffer[m_decodeSampleBufferWriteOffset++] =\n+                    buffer[0][i] * m_sampleScale;\n+        }\n+        break;\n+    }\n+    case 2: {\n+        // optimized code for 2 channels (stereo)\n+        DEBUG_ASSERT(2 <= frame->header.channels);\n+        for (unsigned i = 0; i < frame->header.blocksize; ++i) {\n+            m_decodeSampleBuffer[m_decodeSampleBufferWriteOffset++] =\n+                    buffer[0][i] * m_sampleScale;\n+            m_decodeSampleBuffer[m_decodeSampleBufferWriteOffset++] =\n+                    buffer[1][i] * m_sampleScale;\n+        }\n+        break;\n+    }\n+    default: {\n+        // generic code for multiple channels\n+        DEBUG_ASSERT(getChannelCount() == frame->header.channels);\n+        for (unsigned i = 0; i < frame->header.blocksize; ++i) {\n+            for (unsigned j = 0; j < frame->header.channels; ++j) {\n+                m_decodeSampleBuffer[m_decodeSampleBufferWriteOffset++] =\n+                        buffer[j][i] * m_sampleScale;\n+            }\n+        }\n+    }\n+    }\n+    DEBUG_ASSERT(m_decodeSampleBufferReadOffset <= m_decodeSampleBufferWriteOffset);\n+    return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;\n+}\n+\n+void AudioSourceFLAC::flacMetadata(const FLAC__StreamMetadata *metadata) {\n+    switch (metadata->type) {","path":"src/sources/audiosourceflac.cpp","position":328,"original_position":328,"commit_id":"4651742cb9c548f49ba9b43641ad9edc4bdd2660","original_commit_id":"4651742cb9c548f49ba9b43641ad9edc4bdd2660","user":{"login":"uklotzde","id":1474154,"avatar_url":"https://avatars.githubusercontent.com/u/1474154?v=3","gravatar_id":"","url":"https://api.github.com/users/uklotzde","html_url":"https://github.com/uklotzde","followers_url":"https://api.github.com/users/uklotzde/followers","following_url":"https://api.github.com/users/uklotzde/following{/other_user}","gists_url":"https://api.github.com/users/uklotzde/gists{/gist_id}","starred_url":"https://api.github.com/users/uklotzde/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/uklotzde/subscriptions","organizations_url":"https://api.github.com/users/uklotzde/orgs","repos_url":"https://api.github.com/users/uklotzde/repos","events_url":"https://api.github.com/users/uklotzde/events{/privacy}","received_events_url":"https://api.github.com/users/uklotzde/received_events","type":"User","site_admin":false},"body":"I'm keeping all the E-Mails with your comments that I have not yet worked on. I remember this comment ;)","created_at":"2015-01-29T08:42:46Z","updated_at":"2015-01-29T08:42:46Z","html_url":"https://github.com/mixxxdj/mixxx/pull/411#discussion_r23753122","pull_request_url":"https://api.github.com/repos/mixxxdj/mixxx/pulls/411","_links":{"self":{"href":"https://api.github.com/repos/mixxxdj/mixxx/pulls/comments/23753122"},"html":{"href":"https://github.com/mixxxdj/mixxx/pull/411#discussion_r23753122"},"pull_request":{"href":"https://api.github.com/repos/mixxxdj/mixxx/pulls/411"}}},"pull_request":{"url":"https://api.github.com/repos/mixxxdj/mixxx/pulls/411","id":25228389,"html_url":"https://github.com/mixxxdj/mixxx/pull/411","diff_url":"https://github.com/mixxxdj/mixxx/pull/411.diff","patch_url":"https://github.com/mixxxdj/mixxx/pull/411.patch","issue_url":"https://api.github.com/repos/mixxxdj/mixxx/issues/411","number":411,"state":"open","locked":false,"title":"New SoundSource/AudioSource API","user":{"login":"uklotzde","id":1474154,"avatar_url":"https://avatars.githubusercontent.com/u/1474154?v=3","gravatar_id":"","url":"https://api.github.com/users/uklotzde","html_url":"https://github.com/uklotzde","followers_url":"https://api.github.com/users/uklotzde/followers","following_url":"https://api.github.com/users/uklotzde/following{/other_user}","gists_url":"https://api.github.com/users/uklotzde/gists{/gist_id}","starred_url":"https://api.github.com/users/uklotzde/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/uklotzde/subscriptions","organizations_url":"https://api.github.com/users/uklotzde/orgs","repos_url":"https://api.github.com/users/uklotzde/repos","events_url":"https://api.github.com/users/uklotzde/events{/privacy}","received_events_url":"https://api.github.com/users/uklotzde/received_events","type":"User","site_admin":false},"body":"This is the first step towards a better/cleaner SoundSource API.\r\nThe operations for reading audio data have been moved into the\r\nnew abstract base class AudioSource.\r\n\r\nCurrently SoundSource inherits from AudioSource for technical reasons.\r\nThis is too much much coupling that needs to be broken up. I have\r\nalready some ideas on how to resolve it, but first this work needs to\r\nbe integrated.\r\n\r\nDuring the refactoring I've noticed, that the quality and maturity of the\r\naudio decoders for different file types greatly varies:\r\n\r\nGood/mature:\r\n- FLAC\r\n- Ogg\r\n- Opus\r\n- WavPack\r\n- libsndfile\r\n\r\nMediocre:\r\n- MP3\r\n\r\nUgly/inefficient:\r\n- M4A (2014-12-04: Code has been rewritten without legacy code borrowed from other projects)\r\n- ModPlug\r\n\r\nUntested AudioSource API:\r\n- Apple CoreAudio\r\n- Windows Media Foundation\r\n\r\nUnfinished AudioSource API (just a hack):\r\n- FFmpeg\r\n\r\nEspecially the code for the M4A decoder should be revised and cleaned up as\r\nsoon as possible, followed by a review of the MP3 decoder. Reading audio\r\ndata must work as stable and efficient as possible. This has priority over\r\nthe improvement of the internal software architecture!","created_at":"2014-11-29T23:03:10Z","updated_at":"2015-01-29T08:42:46Z","closed_at":null,"merged_at":null,"merge_commit_sha":"32733fcbfb3190defe483c623f1489ba13f968bb","assignee":null,"milestone":{"url":"https://api.github.com/repos/mixxxdj/mixxx/milestones/2","labels_url":"https://api.github.com/repos/mixxxdj/mixxx/milestones/2/labels","id":754553,"number":2,"title":"1.13","description":"","creator":{"login":"ywwg","id":888940,"avatar_url":"https://avatars.githubusercontent.com/u/888940?v=3","gravatar_id":"","url":"https://api.github.com/users/ywwg","html_url":"https://github.com/ywwg","followers_url":"https://api.github.com/users/ywwg/followers","following_url":"https://api.github.com/users/ywwg/following{/other_user}","gists_url":"https://api.github.com/users/ywwg/gists{/gist_id}","starred_url":"https://api.github.com/users/ywwg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ywwg/subscriptions","organizations_url":"https://api.github.com/users/ywwg/orgs","repos_url":"https://api.github.com/users/ywwg/repos","events_url":"https://api.github.com/users/ywwg/events{/privacy}","received_events_url":"https://api.github.com/users/ywwg/received_events","type":"User","site_admin":false},"open_issues":8,"closed_issues":4,"state":"open","created_at":"2014-08-14T23:06:10Z","updated_at":"2014-11-29T23:05:51Z","due_on":null,"closed_at":null},"commits_url":"https://api.github.com/repos/mixxxdj/mixxx/pulls/411/commits","review_comments_url":"https://api.github.com/repos/mixxxdj/mixxx/pulls/411/comments","review_comment_url":"https://api.github.com/repos/mixxxdj/mixxx/pulls/comments/{number}","comments_url":"https://api.github.com/repos/mixxxdj/mixxx/issues/411/comments","statuses_url":"https://api.github.com/repos/mixxxdj/mixxx/statuses/4651742cb9c548f49ba9b43641ad9edc4bdd2660","head":{"label":"uklotzde:NewSoundSourceAPI","ref":"NewSoundSourceAPI","sha":"4651742cb9c548f49ba9b43641ad9edc4bdd2660","user":{"login":"uklotzde","id":1474154,"avatar_url":"https://avatars.githubusercontent.com/u/1474154?v=3","gravatar_id":"","url":"https://api.github.com/users/uklotzde","html_url":"https://github.com/uklotzde","followers_url":"https://api.github.com/users/uklotzde/followers","following_url":"https://api.github.com/users/uklotzde/following{/other_user}","gists_url":"https://api.github.com/users/uklotzde/gists{/gist_id}","starred_url":"https://api.github.com/users/uklotzde/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/uklotzde/subscriptions","organizations_url":"https://api.github.com/users/uklotzde/orgs","repos_url":"https://api.github.com/users/uklotzde/repos","events_url":"https://api.github.com/users/uklotzde/events{/privacy}","received_events_url":"https://api.github.com/users/uklotzde/received_events","type":"User","site_admin":false},"repo":{"id":11108787,"name":"mixxx","full_name":"uklotzde/mixxx","owner":{"login":"uklotzde","id":1474154,"avatar_url":"https://avatars.githubusercontent.com/u/1474154?v=3","gravatar_id":"","url":"https://api.github.com/users/uklotzde","html_url":"https://github.com/uklotzde","followers_url":"https://api.github.com/users/uklotzde/followers","following_url":"https://api.github.com/users/uklotzde/following{/other_user}","gists_url":"https://api.github.com/users/uklotzde/gists{/gist_id}","starred_url":"https://api.github.com/users/uklotzde/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/uklotzde/subscriptions","organizations_url":"https://api.github.com/users/uklotzde/orgs","repos_url":"https://api.github.com/users/uklotzde/repos","events_url":"https://api.github.com/users/uklotzde/events{/privacy}","received_events_url":"https://api.github.com/users/uklotzde/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/uklotzde/mixxx","description":"Mixxx is Free DJ software that gives you everything you need to perform live mixes.","fork":true,"url":"https://api.github.com/repos/uklotzde/mixxx","forks_url":"https://api.github.com/repos/uklotzde/mixxx/forks","keys_url":"https://api.github.com/repos/uklotzde/mixxx/keys{/key_id}","collaborators_url":"https://api.github.com/repos/uklotzde/mixxx/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/uklotzde/mixxx/teams","hooks_url":"https://api.github.com/repos/uklotzde/mixxx/hooks","issue_events_url":"https://api.github.com/repos/uklotzde/mixxx/issues/events{/number}","events_url":"https://api.github.com/repos/uklotzde/mixxx/events","assignees_url":"https://api.github.com/repos/uklotzde/mixxx/assignees{/user}","branches_url":"https://api.github.com/repos/uklotzde/mixxx/branches{/branch}","tags_url":"https://api.github.com/repos/uklotzde/mixxx/tags","blobs_url":"https://api.github.com/repos/uklotzde/mixxx/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/uklotzde/mixxx/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/uklotzde/mixxx/git/refs{/sha}","trees_url":"https://api.github.com/repos/uklotzde/mixxx/git/trees{/sha}","statuses_url":"https://api.github.com/repos/uklotzde/mixxx/statuses/{sha}","languages_url":"https://api.github.com/repos/uklotzde/mixxx/languages","stargazers_url":"https://api.github.com/repos/uklotzde/mixxx/stargazers","contributors_url":"https://api.github.com/repos/uklotzde/mixxx/contributors","subscribers_url":"https://api.github.com/repos/uklotzde/mixxx/subscribers","subscription_url":"https://api.github.com/repos/uklotzde/mixxx/subscription","commits_url":"https://api.github.com/repos/uklotzde/mixxx/commits{/sha}","git_commits_url":"https://api.github.com/repos/uklotzde/mixxx/git/commits{/sha}","comments_url":"https://api.github.com/repos/uklotzde/mixxx/comments{/number}","issue_comment_url":"https://api.github.com/repos/uklotzde/mixxx/issues/comments/{number}","contents_url":"https://api.github.com/repos/uklotzde/mixxx/contents/{+path}","compare_url":"https://api.github.com/repos/uklotzde/mixxx/compare/{base}...{head}","merges_url":"https://api.github.com/repos/uklotzde/mixxx/merges","archive_url":"https://api.github.com/repos/uklotzde/mixxx/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/uklotzde/mixxx/downloads","issues_url":"https://api.github.com/repos/uklotzde/mixxx/issues{/number}","pulls_url":"https://api.github.com/repos/uklotzde/mixxx/pulls{/number}","milestones_url":"https://api.github.com/repos/uklotzde/mixxx/milestones{/number}","notifications_url":"https://api.github.com/repos/uklotzde/mixxx/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/uklotzde/mixxx/labels{/name}","releases_url":"https://api.github.com/repos/uklotzde/mixxx/releases{/id}","created_at":"2013-07-01T22:22:18Z","updated_at":"2015-01-12T23:41:03Z","pushed_at":"2015-01-28T17:54:33Z","git_url":"git://github.com/uklotzde/mixxx.git","ssh_url":"git@github.com:uklotzde/mixxx.git","clone_url":"https://github.com/uklotzde/mixxx.git","svn_url":"https://github.com/uklotzde/mixxx","homepage":"http://mixxx.org","size":237179,"stargazers_count":0,"watchers_count":0,"language":"C++","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"mixxxdj:master","ref":"master","sha":"4ffbc25fb111380ff1bcaa541b49e3afe6e65bab","user":{"login":"mixxxdj","id":1743769,"avatar_url":"https://avatars.githubusercontent.com/u/1743769?v=3","gravatar_id":"","url":"https://api.github.com/users/mixxxdj","html_url":"https://github.com/mixxxdj","followers_url":"https://api.github.com/users/mixxxdj/followers","following_url":"https://api.github.com/users/mixxxdj/following{/other_user}","gists_url":"https://api.github.com/users/mixxxdj/gists{/gist_id}","starred_url":"https://api.github.com/users/mixxxdj/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mixxxdj/subscriptions","organizations_url":"https://api.github.com/users/mixxxdj/orgs","repos_url":"https://api.github.com/users/mixxxdj/repos","events_url":"https://api.github.com/users/mixxxdj/events{/privacy}","received_events_url":"https://api.github.com/users/mixxxdj/received_events","type":"Organization","site_admin":false},"repo":{"id":10126031,"name":"mixxx","full_name":"mixxxdj/mixxx","owner":{"login":"mixxxdj","id":1743769,"avatar_url":"https://avatars.githubusercontent.com/u/1743769?v=3","gravatar_id":"","url":"https://api.github.com/users/mixxxdj","html_url":"https://github.com/mixxxdj","followers_url":"https://api.github.com/users/mixxxdj/followers","following_url":"https://api.github.com/users/mixxxdj/following{/other_user}","gists_url":"https://api.github.com/users/mixxxdj/gists{/gist_id}","starred_url":"https://api.github.com/users/mixxxdj/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mixxxdj/subscriptions","organizations_url":"https://api.github.com/users/mixxxdj/orgs","repos_url":"https://api.github.com/users/mixxxdj/repos","events_url":"https://api.github.com/users/mixxxdj/events{/privacy}","received_events_url":"https://api.github.com/users/mixxxdj/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/mixxxdj/mixxx","description":"Mixxx is Free DJ software that gives you everything you need to perform live mixes.","fork":false,"url":"https://api.github.com/repos/mixxxdj/mixxx","forks_url":"https://api.github.com/repos/mixxxdj/mixxx/forks","keys_url":"https://api.github.com/repos/mixxxdj/mixxx/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mixxxdj/mixxx/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mixxxdj/mixxx/teams","hooks_url":"https://api.github.com/repos/mixxxdj/mixxx/hooks","issue_events_url":"https://api.github.com/repos/mixxxdj/mixxx/issues/events{/number}","events_url":"https://api.github.com/repos/mixxxdj/mixxx/events","assignees_url":"https://api.github.com/repos/mixxxdj/mixxx/assignees{/user}","branches_url":"https://api.github.com/repos/mixxxdj/mixxx/branches{/branch}","tags_url":"https://api.github.com/repos/mixxxdj/mixxx/tags","blobs_url":"https://api.github.com/repos/mixxxdj/mixxx/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mixxxdj/mixxx/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mixxxdj/mixxx/git/refs{/sha}","trees_url":"https://api.github.com/repos/mixxxdj/mixxx/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mixxxdj/mixxx/statuses/{sha}","languages_url":"https://api.github.com/repos/mixxxdj/mixxx/languages","stargazers_url":"https://api.github.com/repos/mixxxdj/mixxx/stargazers","contributors_url":"https://api.github.com/repos/mixxxdj/mixxx/contributors","subscribers_url":"https://api.github.com/repos/mixxxdj/mixxx/subscribers","subscription_url":"https://api.github.com/repos/mixxxdj/mixxx/subscription","commits_url":"https://api.github.com/repos/mixxxdj/mixxx/commits{/sha}","git_commits_url":"https://api.github.com/repos/mixxxdj/mixxx/git/commits{/sha}","comments_url":"https://api.github.com/repos/mixxxdj/mixxx/comments{/number}","issue_comment_url":"https://api.github.com/repos/mixxxdj/mixxx/issues/comments/{number}","contents_url":"https://api.github.com/repos/mixxxdj/mixxx/contents/{+path}","compare_url":"https://api.github.com/repos/mixxxdj/mixxx/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mixxxdj/mixxx/merges","archive_url":"https://api.github.com/repos/mixxxdj/mixxx/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mixxxdj/mixxx/downloads","issues_url":"https://api.github.com/repos/mixxxdj/mixxx/issues{/number}","pulls_url":"https://api.github.com/repos/mixxxdj/mixxx/pulls{/number}","milestones_url":"https://api.github.com/repos/mixxxdj/mixxx/milestones{/number}","notifications_url":"https://api.github.com/repos/mixxxdj/mixxx/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mixxxdj/mixxx/labels{/name}","releases_url":"https://api.github.com/repos/mixxxdj/mixxx/releases{/id}","created_at":"2013-05-17T15:14:38Z","updated_at":"2015-01-28T15:13:26Z","pushed_at":"2015-01-28T15:13:48Z","git_url":"git://github.com/mixxxdj/mixxx.git","ssh_url":"git@github.com:mixxxdj/mixxx.git","clone_url":"https://github.com/mixxxdj/mixxx.git","svn_url":"https://github.com/mixxxdj/mixxx","homepage":"http://mixxx.org","size":310855,"stargazers_count":227,"watchers_count":227,"language":"C++","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":189,"mirror_url":null,"open_issues_count":35,"forks":189,"open_issues":35,"watchers":227,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/mixxxdj/mixxx/pulls/411"},"html":{"href":"https://github.com/mixxxdj/mixxx/pull/411"},"issue":{"href":"https://api.github.com/repos/mixxxdj/mixxx/issues/411"},"comments":{"href":"https://api.github.com/repos/mixxxdj/mixxx/issues/411/comments"},"review_comments":{"href":"https://api.github.com/repos/mixxxdj/mixxx/pulls/411/comments"},"review_comment":{"href":"https://api.github.com/repos/mixxxdj/mixxx/pulls/comments/{number}"},"commits":{"href":"https://api.github.com/repos/mixxxdj/mixxx/pulls/411/commits"},"statuses":{"href":"https://api.github.com/repos/mixxxdj/mixxx/statuses/4651742cb9c548f49ba9b43641ad9edc4bdd2660"}}}}