Platform library for RETRO

Dependents:   RETRO_RickGame

Revision:
0:6f26c31d8573
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Sound.cpp	Sun Mar 01 05:29:45 2015 +0000
@@ -0,0 +1,515 @@
+/*
+ * (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
+}
\ No newline at end of file