Platform library for RETRO

Dependents:   RETRO_RickGame

Sound.cpp

Committer:
Architect
Date:
2015-03-01
Revision:
0:6f26c31d8573

File content as of revision 0:6f26c31d8573:

/*
 * (C) Copyright 2015 Valentin Ivanov. All rights reserved.
 *
 * This file is part of the RetroPlatform Library
 *
 * The RetroPlatform Library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 * This library is inspired by Gamebuino Library (http://gamebuino.com)
 * from Aurélien Rodot. 
 */
#include "Sound.h"
#include "Utils.h"

#if(NUM_CHANNELS > 0)
uint8_t _rand = 1;
const uint16_t squareWaveInstrument[] = {0x0101, 0x03F7};
const uint16_t noiseInstrument[] = {0x0101, 0x03FF};
const uint16_t* const defaultInstruments[] = {squareWaveInstrument,noiseInstrument};

#define NUM_PITCH 59

const uint8_t _halfPeriods[NUM_PITCH] = {
    /*268,*/253,239,225,213,201,190,179,169,159,150,142,
    134,127,119,113,106,100,95,89,84,80,75,71,67,63,60,
    56,53,50,47,45,42,40,38,36,34,32,30,28,27,25,24,22,
    21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6
};

#endif

Sound::Sound() : speaker(P0_18)
{
    initialize();
}

void Sound::initialize()
{
#if(NUM_CHANNELS > 0)
    volumeMax = VOLUME_GLOBAL_MAX;
    globalVolume = VOLUME_GLOBAL_MAX;
    prescaler = 1;

    speaker.period_us(32);
    speaker.write(0.0);

    for(uint8_t channel=0; channel<NUM_CHANNELS; channel++) {
        chanVolumes[channel] = VOLUME_CHANNEL_MAX;
        changeInstrumentSet(defaultInstruments, channel);
        command(CMD_INSTRUMENT, 0, 0, channel);
    }
    timer.attach_us(this, &Sound::generateOutput, 16); //62500Hz
#endif
}

void Sound::playTrack(const uint16_t* track, uint8_t channel)
{
#if(NUM_CHANNELS > 0)
    if(channel>=NUM_CHANNELS)
        return;
    stopTrack(channel);
    tracks[channel].Cursor = 0;
    tracks[channel].Data = (uint16_t*)track;
    tracks[channel].IsPlaying = true;
#endif
}

void Sound::stopTrack(uint8_t channel)
{
#if(NUM_CHANNELS > 0)
    if(channel>=NUM_CHANNELS)
        return;
    tracks[channel].IsPlaying = false;
    stopSequence(channel);
#endif
}

void Sound::updateTrack(uint8_t channel)
{
#if(NUM_CHANNELS > 0)
    if(channel>=NUM_CHANNELS)
        return;
    if(tracks[channel].IsPlaying && !sequences[channel].IsPlaying) {
        uint16_t data = tracks[channel].Data[tracks[channel].Cursor];
        if(data == 0xFFFF) {
            tracks[channel].IsPlaying = false;
            return;
        }
        uint8_t patternID = data & 0xFF;
        data >>= 8;
        patternPitch[channel] = data;
        playSequence((const uint16_t*)patternSet[channel][patternID], channel);
        tracks[channel].Cursor++;
    }
#endif
}


void Sound::changeSequenceSet(const uint16_t* const* patterns, uint8_t channel)
{
#if(NUM_CHANNELS > 0)
    if(channel>=NUM_CHANNELS)
        return;
    patternSet[channel] = (uint16_t**)patterns;
#endif
}

void Sound::playSequence(const uint16_t* pattern, uint8_t channel)
{
#if(NUM_CHANNELS > 0)
    if(channel>=NUM_CHANNELS)
        return;
    stopSequence(channel);
    sequences[channel].Data = (uint16_t*)pattern;
    sequences[channel].Cursor = 0;
    sequences[channel].IsPlaying = true;
    noteVolume[channel] = 9;
    //reinit commands
    commands[channel].volumeSlideStepDuration = 0;
    commands[channel].arpeggioStepDuration = 0;
    commands[channel].tremoloStepDuration = 0;
#endif
}

void Sound::changeInstrumentSet(const uint16_t* const* instruments, uint8_t channel)
{
#if(NUM_CHANNELS > 0)
    if(channel>=NUM_CHANNELS)
        return;
    instrumentSet[channel] = (uint16_t**)instruments;
#endif
}

void Sound::updateSequence(uint8_t channel)
{
#if(NUM_CHANNELS > 0)
    if(channel>=NUM_CHANNELS)
        return;
    if(sequences[channel].IsPlaying) {
        if(noteDuration[channel]==0) { //if the end of the previous note is reached

            uint16_t data =  sequences[channel].Data[sequences[channel].Cursor];

            if(data == 0) { //end of the pattern reached
                if(sequences[channel].Looping == true) {
                    sequences[channel].Cursor = 0;
                    data =  sequences[channel].Data[sequences[channel].Cursor];
                } else {
                    sequences[channel].IsPlaying = false;
                    if(tracks[channel].IsPlaying) { //if this pattern is part of a track, get the next pattern
                        updateTrack(channel);
                        data = sequences[channel].Data[sequences[channel].Cursor];
                    } else {
                        stopNote(channel);
                        return;
                    }
                }
            }

            while (data & 0x0001) { //read all commands and instrument changes
                data >>= 2;
                uint8_t cmd = data & 0x0F;
                data >>= 4;
                uint8_t X = data & 0x1F;
                data >>= 5;
                int8_t Y = data - 16;
                command(cmd,X,Y,channel);
                sequences[channel].Cursor++;
                data = sequences[channel].Data[sequences[channel].Cursor];
            }
            data >>= 2;

            uint8_t pitch = data & 0x003F;
            data >>= 6;

            uint8_t duration = data;

            playNote(pitch, duration, channel);

            sequences[channel].Cursor++;
        }
    }
#endif
}

void Sound::stopSequence(uint8_t channel)
{
#if(NUM_CHANNELS > 0)
    if(channel>=NUM_CHANNELS)
        return;
    stopNote(channel);
    sequences[channel].IsPlaying = false;
#endif
}

void Sound::command(uint8_t cmd, uint8_t X, int8_t Y, uint8_t channel)
{
#if(NUM_CHANNELS > 0)
    if(channel>=NUM_CHANNELS)
        return;

    switch(cmd) {
        case CMD_VOLUME: //volume
            X = constrain(X, 0, 10);
            noteVolume[channel] = X;
            break;
        case CMD_INSTRUMENT: //instrument
            instruments[channel].Data = (uint16_t*)instrumentSet[channel][X];
            instruments[channel].Length = instruments[channel].Data[0] & 0x00FF; //8 LSB
            instruments[channel].Length *= prescaler;
            instruments[channel].Looping = min(instruments[channel].Data[0] >> 8, instruments[channel].Length); //8 MSB - check that the loop is shorter than the instrument length
            instruments[channel].Looping *= prescaler;
            break;
        case CMD_SLIDE: //volume slide
            commands[channel].volumeSlideStepDuration = X * prescaler;
            commands[channel].volumeSlideStepSize = Y;
            break;
        case CMD_ARPEGGIO:
            commands[channel].arpeggioStepDuration = X * prescaler;
            commands[channel].arpeggioStepSize = Y;
            break;
        case CMD_TREMOLO:
            commands[channel].tremoloStepDuration = X * prescaler;
            commands[channel].tremoloStepSize = Y;
            break;
        default:
            break;
    }
#endif
}

void Sound::playNote(uint8_t pitch, uint8_t duration, uint8_t channel)
{
#if(NUM_CHANNELS > 0)
    if(channel>=NUM_CHANNELS)
        return;

    //set note
    notePitch[channel] = pitch;
    noteDuration[channel] = duration * prescaler;
    notePlaying[channel] = true;

    //reinit vars
    instruments[channel].NextChange = 0;
    instruments[channel].Cursor = 0;

    commands[channel].Counter = 0;

    _chanState[channel] = true;
#endif
}

void Sound::stopNote(uint8_t channel)
{
#if(NUM_CHANNELS > 0)
    if(channel>=NUM_CHANNELS)
        return;
    notePlaying[channel] = false;
    //counters
    noteDuration[channel] = 0;
    instruments[channel].Cursor = 0;
    commands[channel].Counter = 0;
    //output
    _chanOutput[channel] = 0;
    _chanOutputVolume[channel] = 0;
    _chanState[channel] = false;
    updateOutput();
#endif
}

void Sound::updateNote(uint8_t channel)
{
#if(NUM_CHANNELS > 0)
    if(channel>=NUM_CHANNELS)
        return;
    if (notePlaying[channel]) {

        if(noteDuration[channel] == 0) {
            stopNote(channel);
            return;
        } else {
            noteDuration[channel]--;
        }

        if (instruments[channel].NextChange == 0) {

            //read the step data from the progmem and decode it
            uint16_t thisStep = instruments[channel].Data[1 + instruments[channel].Cursor];

            stepVolume[channel] = thisStep & 0x0007;
            thisStep >>= 3;

            uint8_t stepNoise = thisStep & 0x0001;
            thisStep >>= 1;

            uint8_t stepDuration = thisStep & 0x003F;
            thisStep >>= 6;

            stepPitch[channel] = thisStep;

            //apply the step settings
            instruments[channel].NextChange = stepDuration * prescaler;

            _chanNoise[channel] = stepNoise;

            instruments[channel].Cursor++;

            if (instruments[channel].Cursor >= instruments[channel].Length) {
                if (instruments[channel].Looping) {
                    instruments[channel].Cursor = instruments[channel].Length - instruments[channel].Looping;
                } else {
                    stopNote(channel);
                }
            }
        }
        instruments[channel].NextChange--;

        commands[channel].Counter++;

        outputPitch[channel] = notePitch[channel] + stepPitch[channel] + patternPitch[channel];

        if(commands[channel].arpeggioStepDuration)
            outputPitch[channel] += commands[channel].Counter / commands[channel].arpeggioStepDuration * commands[channel].arpeggioStepSize;

        outputPitch[channel] = outputPitch[channel] % NUM_PITCH; //wrap

        //volume
        outputVolume[channel] = noteVolume[channel];
        if(commands[channel].volumeSlideStepDuration)
            outputVolume[channel] += commands[channel].Counter / commands[channel].volumeSlideStepDuration * commands[channel].volumeSlideStepSize;

        if(commands[channel].tremoloStepDuration)
            outputVolume[channel] += ((commands[channel].Counter / commands[channel].tremoloStepDuration) % 2) * commands[channel].tremoloStepSize;

        outputVolume[channel] = constrain(outputVolume[channel], 0, 9);

        if(notePitch[channel] == 63)
            outputVolume[channel] = 0;

        __disable_irq();
        _chanHalfPeriod[channel] = _halfPeriods[outputPitch[channel]];
        _chanOutput[channel] = _chanOutputVolume[channel] = outputVolume[channel] * globalVolume * chanVolumes[channel] * stepVolume[channel];
        __enable_irq();
    }
#endif
}

void Sound::setChannelHalfPeriod(uint8_t channel, uint8_t halfPeriod)
{
#if(NUM_CHANNELS > 0)
    if(channel>=NUM_CHANNELS)
        return;
    _chanHalfPeriod[channel] = halfPeriod;
    _chanState[channel] = false;
    _chanCount[channel] = 0;
    updateOutput();
#endif
}


void Sound::generateOutput()
{
#if(NUM_CHANNELS > 0)
    boolean outputChanged = false;
    //no for loop here, for the performance sake (this function runs 15 000 times per second...)
    //CHANNEL 0
    if (_chanOutputVolume[0]) {
        _chanCount[0]++;
        if (_chanCount[0] >= _chanHalfPeriod[0]) {
            outputChanged = true;
            _chanState[0] = !_chanState[0];
            _chanCount[0] = 0;
            if (_chanNoise[0]) {
                _rand = 67 * _rand + 71;
                _chanOutput[0] = _rand % _chanOutputVolume[0];
            }
        }
    }

    //CHANNEL 1
#if (NUM_CHANNELS > 1)
    if (_chanOutputVolume[1]) {
        _chanCount[1]++;
        if (_chanCount[1] >= _chanHalfPeriod[1]) {
            outputChanged = true;
            _chanState[1] = !_chanState[1];
            _chanCount[1] = 0;
            if (_chanNoise[1]) {
                _rand = 67 * _rand + 71;
                _chanOutput[1] = _rand % _chanOutputVolume[1];
            }
        }
    }
#endif

    //CHANNEL 2
#if (NUM_CHANNELS > 2)
    if (_chanOutputVolume[2]) {
        _chanCount[2]++;
        if (_chanCount[2] >= _chanHalfPeriod[2]) {
            outputChanged = true;
            _chanState[2] = !_chanState[2];
            _chanCount[2] = 0;
            if (_chanNoise[2]) {
                _rand = 67 * _rand + 71;
                _chanOutput[2] = _rand % _chanOutputVolume[2];
            }
        }
    }
#endif

    //CHANNEL 3
#if (NUM_CHANNELS > 3)
    if (_chanOutputVolume[3]) {
        _chanCount[3]++;
        if (_chanCount[3] >= _chanHalfPeriod[3]) {
            outputChanged = true;
            _chanState[3] = !_chanState[3];
            _chanCount[3] = 0;
            if (_chanNoise[3]) {
                _rand = 67 * _rand + 71;
                _chanOutput[3] = _rand % _chanOutputVolume[3];
            }
        }
    }
#endif

    if (outputChanged) {
        updateOutput();
    }
#endif
}

void Sound::updateOutput()
{
#if(NUM_CHANNELS > 0)
    uint8_t output = 0;

    //CHANNEL 0
    if (_chanState[0]) {
        output += _chanOutput[0];
    }

    //CHANNEL 1
#if (NUM_CHANNELS > 1)
    if (_chanState[1]) {
        output += _chanOutput[1];
    }
#endif

    //CHANNEL 2
#if (NUM_CHANNELS > 2)
    if (_chanState[2]) {
        output += _chanOutput[2];
    }
#endif

    //CHANNEL 3
#if (NUM_CHANNELS > 3)
    if (_chanState[3]) {
        output += _chanOutput[3];
    }
#endif
    speaker.write(output/255.0);
#endif
}


void Sound::setVolume(int8_t volume)
{
#if NUM_CHANNELS > 0
    globalVolume = volume % (volumeMax+1);
#endif
}

uint8_t Sound::getVolume()
{
#if NUM_CHANNELS > 0
    return globalVolume;
#else
    return 0;
#endif
}

void Sound::setVolume(int8_t volume, uint8_t channel)
{
#if(NUM_CHANNELS > 0)
    if(channel>=NUM_CHANNELS)
        return;
    volume = (volume > VOLUME_CHANNEL_MAX) ? VOLUME_CHANNEL_MAX : volume;
    volume = (volume < 0) ? 0 : volume;
    chanVolumes[channel] = volume;
#endif
}

uint8_t Sound::getVolume(uint8_t channel)
{
#if(NUM_CHANNELS > 0)
    if(channel>=NUM_CHANNELS)
        return 255;
    return (chanVolumes[channel]);
#else
    return 0;
#endif
}