Advertisement
Guest User

Untitled

a guest
May 26th, 2018
74
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 16.01 KB | None | 0 0
  1. using MicroLibrary;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using GBAMusic.Core.M4A;
  6. using static GBAMusic.Core.M4A.M4AStructs;
  7.  
  8. namespace GBAMusic.Core
  9. {
  10.     enum State
  11.     {
  12.         Playing,
  13.         Paused,
  14.         Stopped
  15.     }
  16.  
  17.     internal class MusicPlayer
  18.     {
  19.         FMOD.System system;
  20.         FMOD.ChannelGroup parentGroup;
  21.  
  22.         Instrument[] dsInstruments;
  23.         Instrument[] gbInstruments;
  24.         Track[] tracks;
  25.  
  26.         Dictionary<uint, FMOD.Sound> sounds;
  27.         internal static readonly uint SQUARE12_ID = 0xFFFFFFFF,
  28.             SQUARE25_ID = SQUARE12_ID - 1,
  29.             SQUARE50_ID = SQUARE25_ID - 1,
  30.             SQUARE75_ID = SQUARE50_ID - 1,
  31.             NOISE0_ID = SQUARE75_ID - 1,
  32.             NOISE1_ID = NOISE0_ID - 1;
  33.  
  34.         ushort tempo;
  35.         MicroTimer timer;
  36.  
  37.         internal static MusicPlayer Instance { get; private set; }
  38.         internal MusicPlayer()
  39.         {
  40.             if (Instance != null) return;
  41.             Instance = this;
  42.             FMOD.Factory.System_Create(out system);
  43.             system.init(32, FMOD.INITFLAGS.NORMAL, (IntPtr)0);
  44.             system.createChannelGroup(null, out parentGroup);
  45.             parentGroup.setVolume(0.5f);
  46.  
  47.             dsInstruments = new Instrument[28];
  48.             gbInstruments = new Instrument[4];
  49.             for (int i = 0; i < 28; i++)
  50.                 dsInstruments[i] = new Instrument();
  51.             for (int i = 0; i < 4; i++)
  52.                 gbInstruments[i] = new Instrument();
  53.             tracks = new Track[16];
  54.             for (int i = 0; i < 16; i++)
  55.                 tracks[i] = new Track(system, parentGroup);
  56.             sounds = new Dictionary<uint, FMOD.Sound>();
  57.             PSGSquare();
  58.             PSGNoise();
  59.  
  60.             timer = new MicroTimer();
  61.             timer.MicroTimerElapsed += (o, e) => PlayLoop();
  62.         }
  63.  
  64.         void PSGSquare()
  65.         {
  66.             byte[] simple = { 1, 2, 4, 6 };
  67.             uint len = 0x100;
  68.             var buf = new byte[16 + len + 16]; // FMOD API requires 16 bytes of padding on each side
  69.  
  70.             for (uint i = 0; i < 4; i++) // Squares
  71.             {
  72.                 for (int j = 0; j < len; j++)
  73.                     buf[16 + j] = (byte)(j < simple[i] * 0x20 ? 0xBF : 0x0);
  74.                 var ex = new FMOD.CREATESOUNDEXINFO()
  75.                 {
  76.                     defaultfrequency = 44100, //(int)(44100 * Math.Pow(2, (9 / 12f))), // Root note 69
  77.                     format = FMOD.SOUND_FORMAT.PCM8,
  78.                     length = len,
  79.                     numchannels = 1
  80.                 };
  81.                 system.createSound(buf, FMOD.MODE.OPENMEMORY | FMOD.MODE.OPENRAW | FMOD.MODE.LOOP_NORMAL | FMOD.MODE.LOWMEM, ref ex, out FMOD.Sound snd);
  82.                 sounds.Add(SQUARE12_ID - i, snd);
  83.             }
  84.         }
  85.         void PSGNoise()
  86.         {
  87.             uint[] simple = { 32768, 256 };
  88.             var rand = new Random();
  89.  
  90.             for (uint i = 0; i < 2; i++)
  91.             {
  92.                 uint len = simple[i];
  93.                 var buf = new byte[16 + len + 16]; // FMOD API requires 16 bytes of padding on each side
  94.  
  95.                 for (int j = 0; j < len; j++)
  96.                     buf[j + 16] = (byte)rand.Next();
  97.  
  98.                 var ex = new FMOD.CREATESOUNDEXINFO()
  99.                 {
  100.                     defaultfrequency = 22050,
  101.                     format = FMOD.SOUND_FORMAT.PCM8,
  102.                     length = len,
  103.                     numchannels = 1
  104.                 };
  105.                 system.createSound(buf, FMOD.MODE.OPENMEMORY | FMOD.MODE.OPENRAW | FMOD.MODE.LOOP_NORMAL | FMOD.MODE.LOWMEM, ref ex, out FMOD.Sound snd);
  106.                 sounds.Add(NOISE0_ID - i, snd);
  107.             }
  108.         }
  109.  
  110.         SongHeader header;
  111.         VoiceTable voiceTable;
  112.  
  113.         internal State State { get; private set; }
  114.         internal delegate void SongEndedEvent();
  115.         internal event SongEndedEvent SongEnded;
  116.  
  117.         internal void Play(ushort song)
  118.         {
  119.             Stop();
  120.  
  121.             header = ROM.Instance.ReadStruct<SongHeader>(ROM.Instance.ReadPointer(ROM.Instance.Config.SongTable + ((uint)8 * song)));
  122.             Array.Resize(ref header.Tracks, header.NumTracks); // Not really necessary
  123.             for (int i = 0; i < header.NumTracks; i++)
  124.                 tracks[i].Init(header.Tracks[i]);
  125.             voiceTable = new VoiceTable();
  126.             voiceTable.LoadPCMSamples(header.VoiceTable, system, sounds);
  127.  
  128.             new VoiceTableSaver(voiceTable, sounds); // Testing
  129.  
  130.             SetTempo(120);
  131.             timer.Start();
  132.             State = State.Playing;
  133.         }
  134.         internal void Pause()
  135.         {
  136.             if (State == State.Paused)
  137.             {
  138.                 timer.Start();
  139.                 State = State.Playing;
  140.             }
  141.             else
  142.             {
  143.                 Stop();
  144.                 State = State.Paused;
  145.             }
  146.         }
  147.         internal void Stop()
  148.         {
  149.             timer.StopAndWait();
  150.             foreach (Instrument i in dsInstruments.Union(gbInstruments))
  151.                 i.Stop();
  152.             State = State.Stopped;
  153.         }
  154.         void PlayLoop()
  155.         {
  156.             bool allStopped = true;
  157.             for (int i = 0; i < header.NumTracks; i++)
  158.             {
  159.                 Track track = tracks[i];
  160.                 if (!track.Stopped)
  161.                     allStopped = false;
  162.                 while (track.Delay == 0 && !track.Stopped)
  163.                     ExecuteNext(track);
  164.                 track.Tick();
  165.             }
  166.             system.update();
  167.             if (allStopped)
  168.             {
  169.                 Stop();
  170.                 SongEnded?.Invoke();
  171.             }
  172.         }
  173.  
  174.         internal (ushort, uint[], byte[], byte[], byte[][], float[], byte[], byte[], int[], float[], string[]) GetTrackStates()
  175.         {
  176.             var positions = new uint[header.NumTracks];
  177.             var volumes = new byte[header.NumTracks];
  178.             var delays = new byte[header.NumTracks];
  179.             var notes = new byte[header.NumTracks][];
  180.             var velocities = new float[header.NumTracks];
  181.             var voices = new byte[header.NumTracks];
  182.             var modulations = new byte[header.NumTracks];
  183.             var bends = new int[header.NumTracks];
  184.             var pans = new float[header.NumTracks];
  185.             var types = new string[header.NumTracks];
  186.             for (int i = 0; i < header.NumTracks; i++)
  187.             {
  188.                 positions[i] = tracks[i].Position;
  189.                 volumes[i] = tracks[i].Volume;
  190.                 delays[i] = tracks[i].Delay;
  191.                 voices[i] = tracks[i].Voice;
  192.                 modulations[i] = tracks[i].MODDepth;
  193.                 bends[i] = tracks[i].Bend * tracks[i].BendRange;
  194.                 types[i] = voiceTable[tracks[i].Voice].ToString();
  195.  
  196.                 Instrument[] instruments = tracks[i].Instruments.Clone().ToArray();
  197.                 bool none = instruments.Length == 0;
  198.                 Instrument loudest = none ? null : instruments.OrderByDescending(ins => ins.Volume).ElementAt(0);
  199.                 pans[i] = none ? tracks[i].Pan / 64f : loudest.Panpot;
  200.                 notes[i] = none ? new byte[0] : instruments.Where(ins => !ins.Releasing).Select(ins => ins.Note).Distinct().ToArray();
  201.                 velocities[i] = none ? 0 : loudest.Volume * (volumes[i] / 127f);
  202.             }
  203.             return (tempo, positions, volumes, delays, notes, velocities, voices, modulations, bends, pans, types);
  204.         }
  205.  
  206.         void SetTempo(ushort t)
  207.         {
  208.             if (t > 510) return;
  209.             tempo = t;
  210.             timer.Interval = (long)(2.5 * 1000 * 1000) / t;
  211.         }
  212.         byte WaitFromCMD(byte startCMD, byte cmd)
  213.         {
  214.             byte[] added = { 4, 4, 2, 2 };
  215.             byte wait = (byte)(cmd - startCMD);
  216.             byte add = wait > 24 ? (byte)24 : wait;
  217.             for (int i = 24 + 1; i <= wait; i++)
  218.                 add += added[i % 4];
  219.             return add;
  220.         }
  221.  
  222.         void PlayNote(Track track, byte note, byte velocity, byte addedDelay = 0)
  223.         {
  224.             // new_note is for drums to have a nice root note
  225.             byte new_note = track.PrevNote = note;
  226.             track.PrevVelocity = velocity;
  227.  
  228.             Instrument instrument = null;
  229.             Voice voice = voiceTable[track.Voice].Instrument;
  230.  
  231.             Read:
  232.             switch (voice.VoiceType)
  233.             {
  234.                 case 0x0:
  235.                 case 0x8:
  236.                     foreach (Instrument i in dsInstruments)
  237.                         if (!i.Playing)
  238.                         {
  239.                             instrument = i;
  240.                             break;
  241.                         }
  242.                     if (instrument == null) // None free:
  243.                         foreach (Instrument i in dsInstruments)
  244.                             if (track.Priority > i.Track.Priority)
  245.                                 instrument = i;
  246.                     if (instrument == null) // None prioritized:
  247.                         foreach (Instrument i in dsInstruments)
  248.                             if (i.Releasing)
  249.                                 instrument = i;
  250.                     if (instrument == null) // None available; kill newest:
  251.                         instrument = dsInstruments.OrderBy(i => i.Age).ElementAt(0);
  252.                     break;
  253.                 case 0x1:
  254.                 case 0x9:
  255.                     instrument = gbInstruments[0];
  256.                     break;
  257.                 case 0x2:
  258.                 case 0xA:
  259.                     instrument = gbInstruments[1];
  260.                     break;
  261.                 case 0x3:
  262.                 case 0xB:
  263.                     instrument = gbInstruments[2];
  264.                     break;
  265.                 case 0x4:
  266.                 case 0xC:
  267.                     instrument = gbInstruments[3];
  268.                     break;
  269.                 case 0x40:
  270.                     var split = (Split)voice;
  271.                     var multi = (SMulti)voiceTable[track.Voice];
  272.                     byte ins = ROM.Instance.ReadByte(split.Keys + note);
  273.                     voice = multi.Table[ins].Instrument;
  274.                     new_note = note; // In case there is a multi within a drum
  275.                     goto Read;
  276.                 case 0x80:
  277.                     var drum = (SDrum)voiceTable[track.Voice];
  278.                     voice = drum.Table[note].Instrument;
  279.                     new_note = 60; // See, I told you it was nice
  280.                     goto Read;
  281.             }
  282.  
  283.             instrument.Stop();
  284.             instrument.Play(track, system, sounds, voice, new_note, track.RunCmd == 0xCF ? (byte)0xFF : WaitFromCMD(0xD0, track.RunCmd));
  285.         }
  286.  
  287.         void ExecuteNext(Track track)
  288.         {
  289.             byte cmd = track.ReadByte();
  290.             if (cmd >= 0xBD) // Commands that work within running status
  291.                 track.RunCmd = cmd;
  292.  
  293.             #region TIE & Notes
  294.  
  295.             if (track.RunCmd >= 0xCF && cmd < 0x80) // Within running status
  296.             {
  297.                 var o = track.Position;
  298.                 byte peek1 = track.ReadByte(),
  299.                     peek2 = track.ReadByte();
  300.                 track.SetOffset(o);
  301.                 if (peek1 >= 128) PlayNote(track, cmd, track.PrevVelocity);
  302.                 else if (peek2 >= 128) PlayNote(track, cmd, track.ReadByte());
  303.                 else PlayNote(track, cmd, track.ReadByte(), track.ReadByte());
  304.             }
  305.             else if (cmd >= 0xCF)
  306.             {
  307.                 var o = track.Position;
  308.                 byte peek1 = track.ReadByte(),
  309.                     peek2 = track.ReadByte(),
  310.                     peek3 = track.ReadByte();
  311.                 track.SetOffset(o);
  312.                 if (peek1 >= 128) PlayNote(track, track.PrevNote, track.PrevVelocity);
  313.                 else if (peek2 >= 128) PlayNote(track, track.ReadByte(), track.PrevVelocity);
  314.                 else if (peek3 >= 128) PlayNote(track, track.ReadByte(), track.ReadByte());
  315.                 else PlayNote(track, track.ReadByte(), track.ReadByte(), track.ReadByte());
  316.             }
  317.  
  318.             #endregion
  319.  
  320.             #region Waits
  321.  
  322.             else if (cmd >= 0x80 && cmd <= 0xB0)
  323.             {
  324.                 track.Delay = WaitFromCMD(0x80, cmd);
  325.             }
  326.  
  327.             #endregion
  328.  
  329.             #region Commands
  330.  
  331.             else if (track.RunCmd < 0xCF && cmd < 0x80) // Commands within running status
  332.             {
  333.                 switch (track.RunCmd)
  334.                 {
  335.                     case 0xBD: track.Voice = cmd; break; // VOICE
  336.                     case 0xBE: track.SetVolume(cmd); break; // VOL
  337.                     case 0xBF: track.SetPan(cmd); break; // PAN
  338.                     case 0xC0: track.SetBend(cmd); break; // BEND
  339.                     case 0xC1: track.SetBendRange(cmd); break; // BENDR
  340.                     case 0xC4: track.SetMODDepth(cmd); break; // MOD
  341.                     case 0xC5: track.SetMODType(cmd); break; // MODT
  342.                     case 0xCD: track.ReadByte(); break; // XCMD
  343.                 }
  344.             }
  345.             else if (cmd > 0xB0 && cmd < 0xCF)
  346.             {
  347.                 switch (cmd)
  348.                 {
  349.                     case 0xB1: track.Stopped = true; break; // FINE
  350.                     case 0xB2: track.SetOffset(track.ReadPointer()); break; // GOTO
  351.                     case 0xB3: // PATT
  352.                         uint jump = track.ReadPointer();
  353.                         track.EndOfPattern = track.Position;
  354.                         track.SetOffset(jump);
  355.                         break;
  356.                     case 0xB4: // PEND
  357.                         if (track.EndOfPattern != 0)
  358.                         {
  359.                             track.SetOffset(track.EndOfPattern);
  360.                             track.EndOfPattern = 0;
  361.                         }
  362.                         break;
  363.                     case 0xB5: track.ReadByte(); break; // REPT
  364.                     case 0xB9: track.ReadByte(); track.ReadByte(); track.ReadByte(); break; // MEMACC
  365.                     case 0xBA: track.SetPriority(track.ReadByte()); break; // PRIO
  366.                     case 0xBB: SetTempo((ushort)(track.ReadByte() * 2)); break; // TEMPO
  367.                     case 0xBC: track.ReadByte(); break; // KEYSH
  368.                                                         // Commands that work within running status:
  369.                     case 0xBD: track.Voice = cmd; break; // VOICE
  370.                     case 0xBE: track.SetVolume(track.ReadByte()); break; // VOL
  371.                     case 0xBF: track.SetPan(track.ReadByte()); break; // PAN
  372.                     case 0xC0: track.SetBend(track.ReadByte()); break; // BEND
  373.                     case 0xC1: track.SetBendRange(track.ReadByte()); break; // BENDR
  374.                     case 0xC2: track.ReadByte(); break; // LFOS
  375.                     case 0xC3: track.ReadByte(); break; // LFODL
  376.                     case 0xC4: track.SetMODDepth(track.ReadByte()); break;
  377.                     case 0xC5: track.SetMODType(track.ReadByte()); break;
  378.                     case 0xC8: track.ReadByte(); break; // TUNE
  379.                     case 0xCD: track.ReadByte(); track.ReadByte(); break; // XCMD
  380.                     case 0xCE: // EOT
  381.                         Instrument i = null;
  382.                         if (track.PeekByte() < 128)
  383.                         {
  384.                             byte note = track.ReadByte();
  385.                             i = track.Instruments.FirstOrDefault(ins => ins.NoteDuration == 0xFF && ins.Note == note);
  386.                             track.PrevNote = note;
  387.                         }
  388.                         else
  389.                         {
  390.                             i = track.Instruments.FirstOrDefault(ins => ins.NoteDuration == 0xFF);
  391.                         }
  392.                         if (i != null)
  393.                             i.TriggerRelease();
  394.                         break;
  395.                     default: Console.WriteLine("Invalid command: 0x{0:X} = {1}", track.Position, cmd); break;
  396.                 }
  397.             }
  398.  
  399.             #endregion
  400.         }
  401.     }
  402. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement