root/src/kits/midi2/MidiLocalProducer.cpp
/*
 * Copyright 2005-2006, Haiku.
 * 
 * Copyright (c) 2002-2004 Matthijs Hollemans
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Matthijs Hollemans
 */

#include <stdlib.h>

#include "debug.h"
#include <MidiConsumer.h>
#include <MidiProducer.h>
#include <MidiRoster.h>
#include "protocol.h"


BMidiLocalProducer::BMidiLocalProducer(const char* name)
        : BMidiProducer(name)
{
        TRACE(("BMidiLocalProducer::BMidiLocalProducer"))

        fIsLocal = true;
        fRefCount = 1;

        BMidiRoster::MidiRoster()->CreateLocal(this);
}


BMidiLocalProducer::~BMidiLocalProducer()
{
        TRACE(("BMidiLocalProducer::~BMidiLocalProducer"))

        BMidiRoster::MidiRoster()->DeleteLocal(this);
}


void
BMidiLocalProducer::Connected(BMidiConsumer* cons)
{
        ASSERT(cons != NULL)
        TRACE(("Connected() %" B_PRId32 " to %" B_PRId32 "", ID(), cons->ID()))

        // Do nothing.
}


void
BMidiLocalProducer::Disconnected(BMidiConsumer* cons)
{
        ASSERT(cons != NULL)
        TRACE(("Disconnected() %" B_PRId32 " from %" B_PRId32 "", ID(), cons->ID()))

        // Do nothing.
}


void
BMidiLocalProducer::SprayData(void* data, size_t length,
        bool atomic, bigtime_t time) const
{
        SprayEvent(data, length, atomic, time);
}


void
BMidiLocalProducer::SprayNoteOff(uchar channel, uchar note,
        uchar velocity, bigtime_t time) const
{
        if (channel < 16) {
                uchar data[3];
                data[0] = B_NOTE_OFF + channel;
                data[1] = note;
                data[2] = velocity;

                SprayEvent(&data, 3, true, time);
        } else {
                debugger("invalid MIDI channel");
        }
}


void
BMidiLocalProducer::SprayNoteOn(uchar channel, uchar note,
        uchar velocity, bigtime_t time) const
{
        if (channel < 16) {
                uchar data[3];
                data[0] = B_NOTE_ON + channel;
                data[1] = note;
                data[2] = velocity;

                SprayEvent(&data, 3, true, time);
        } else {
                debugger("invalid MIDI channel");
        }
}


void
BMidiLocalProducer::SprayKeyPressure(uchar channel, uchar note,
        uchar pressure, bigtime_t time) const
{
        if (channel < 16) {
                uchar data[3];
                data[0] = B_KEY_PRESSURE + channel;
                data[1] = note;
                data[2] = pressure;

                SprayEvent(&data, 3, true, time);
        } else {
                debugger("invalid MIDI channel");
        }
}


void
BMidiLocalProducer::SprayControlChange(uchar channel,
        uchar controlNumber, uchar controlValue, bigtime_t time) const
{
        if (channel < 16) {
                uchar data[3];
                data[0] = B_CONTROL_CHANGE + channel;
                data[1] = controlNumber;
                data[2] = controlValue;

                SprayEvent(&data, 3, true, time);
        } else {
                debugger("invalid MIDI channel");
        }
}


void
BMidiLocalProducer::SprayProgramChange(uchar channel,
        uchar programNumber, bigtime_t time) const
{
        if (channel < 16) {
                uchar data[2];
                data[0] = B_PROGRAM_CHANGE + channel;
                data[1] = programNumber;

                SprayEvent(&data, 2, true, time);
        } else {
                debugger("invalid MIDI channel");
        }
}


void
BMidiLocalProducer::SprayChannelPressure(uchar channel,
        uchar pressure, bigtime_t time) const
{
        if (channel < 16) {
                uchar data[2];
                data[0] = B_CHANNEL_PRESSURE + channel;
                data[1] = pressure;

                SprayEvent(&data, 2, true, time);
        } else {
                debugger("invalid MIDI channel");
        }
}


void
BMidiLocalProducer::SprayPitchBend(uchar channel,
        uchar lsb, uchar msb, bigtime_t time) const
{
        if (channel < 16) {
                uchar data[3];
                data[0] = B_PITCH_BEND + channel;
                data[1] = lsb;
                data[2] = msb;

                SprayEvent(&data, 3, true, time);
        } else {
                debugger("invalid MIDI channel");
        }
}


void
BMidiLocalProducer::SpraySystemExclusive(void* data,
        size_t length, bigtime_t time) const
{
        SprayEvent(data, length, true, time, true);
}


void
BMidiLocalProducer::SpraySystemCommon(uchar status, uchar data1,
        uchar data2, bigtime_t time) const
{
        size_t len;
        uchar data[3];
        data[0] = status;
        data[1] = data1;
        data[2] = data2;

        switch (status) {
                case B_TUNE_REQUEST:
                case B_SYS_EX_END:
                        len = 1;
                        break;

                case B_CABLE_MESSAGE:
                case B_MIDI_TIME_CODE:
                case B_SONG_SELECT:
                        len = 2;
                        break;

                case B_SONG_POSITION:
                        len = 3;
                        break;

                default:
                        debugger("invalid system common status");
                        len = 0;
        }

        SprayEvent(&data, len, true, time);
}


void
BMidiLocalProducer::SpraySystemRealTime(uchar status,
        bigtime_t time) const
{
        if (status >= B_TIMING_CLOCK)
                SprayEvent(&status, 1, true, time);
        else
                debugger("invalid real time status");
}


void
BMidiLocalProducer::SprayTempoChange(int32 beatsPerMinute,
        bigtime_t time) const
{
        int32 tempo = 60000000 / beatsPerMinute;

        uchar data[6];
        data[0] = 0xFF;
        data[1] = 0x51;
        data[2] = 0x03;
        data[3] = tempo >> 16;
        data[4] = tempo >> 8;
        data[5] = tempo;

        SprayEvent(&data, 6, true, time);
}

//------------------------------------------------------------------------------

void BMidiLocalProducer::_Reserved1() { }
void BMidiLocalProducer::_Reserved2() { }
void BMidiLocalProducer::_Reserved3() { }
void BMidiLocalProducer::_Reserved4() { }
void BMidiLocalProducer::_Reserved5() { }
void BMidiLocalProducer::_Reserved6() { }
void BMidiLocalProducer::_Reserved7() { }
void BMidiLocalProducer::_Reserved8() { }

//------------------------------------------------------------------------------

void
BMidiLocalProducer::SprayEvent(const void* data, size_t length,
        bool atomic, bigtime_t time, bool sysex) const
{
        if (LockProducer()) {
                if (CountConsumers() > 0) {
                        // We don't just send the MIDI event data to all connected
                        // consumers, we also send a header. The header contains our
                        // ID (4 bytes), the consumer's ID (4 bytes), the performance
                        // time (8 bytes), whether the data is atomic (1 byte), and
                        // padding (3 bytes). The MIDI event data follows the header.

                        size_t buf_size = 20 + length;
                        if (sysex) {
                                // add 0xF0 and 0xF7 markers
                                buf_size += 2;
                        }

                        uint8* buffer = (uint8*)malloc(buf_size);
                        if (buffer != NULL) {
                                *((uint32*)    (buffer +  0)) = fId;
                                *((bigtime_t*) (buffer +  8)) = time;
                                *((uint32*)    (buffer + 16)) = 0;
                                *((bool*)      (buffer + 16)) = atomic;

                                if (sysex) {
                                        *((uint8*) (buffer + 20)) = B_SYS_EX_START;
                                        if (data != NULL)
                                                memcpy(buffer + 21, data, length);

                                        *((uint8*) (buffer + buf_size - 1)) = B_SYS_EX_END;
                                } else if (data != NULL) {
                                        memcpy(buffer + 20, data, length);
                                }

                                for (int32 t = 0; t < CountConsumers(); ++t) {
                                        BMidiConsumer* cons = ConsumerAt(t);
                                        *((uint32*) (buffer + 4)) = cons->fId;

                                        #ifdef DEBUG
                                        printf("*** spraying: ");
                                        for (uint32 t = 0; t < buf_size; ++t)
                                        {
                                                printf("%02X, ", buffer[t]);
                                        }
                                        printf("\n");
                                        #endif

                                        write_port(cons->fPort, 0, buffer, buf_size);
                                }                               

                                free(buffer);
                        }
                }

                UnlockProducer();
        }
}