├── .gitignore ├── ArduinoProject ├── ArduinoProject.ino └── test_other_timers_sn76489 ├── CIrcuit Screen Grab.png ├── Example VGM Tunes ├── 13 Amps.vgm ├── 1942.vgm ├── Addicts Anthem 3.vgm ├── Addicts Anthem Miami Remix 89.vgm ├── Addicts Anthem Remix.vgm ├── Addicts Anthem.vgm ├── Blue Monday Remix.vgm ├── Chariots.vgm ├── Cheers.vgm ├── Circle in the Sand.vgm ├── Cold Tea.vgm ├── Dambuster March.vgm ├── Disk User.vgm ├── Domino Dancing.vgm ├── Dreamscape.vgm ├── Enjoy the silence.vgm ├── Equinoxe5.vgm ├── Flash Dance.vgm ├── Ghostbusters.vgm ├── Golden Brown.vgm ├── Good King.vgm ├── Newsong.vgm ├── Only You.vgm ├── Pride.vgm ├── Raiders of the lost ark.vgm ├── Rent.vgm ├── Sweet Dreams.vgm ├── Thriller.vgm ├── True Colors.vgm ├── Valkeris.vgm ├── Win Again.vgm ├── airwolf.vgm ├── ateam.vgm ├── dallas.vgm ├── doctor doctor.vgm ├── elsewhere.vgm ├── in the mood.vgm ├── its a sin.vgm ├── stop.vgm └── tubular bells.vgm ├── LICENSE ├── Main Circuit.bmp ├── Main Circuit.dwg ├── Main Circuit.dxf ├── Pictures ├── DSC_0740.JPG ├── DSC_0741.JPG ├── DSC_0742.JPG ├── DSC_1048.JPG └── DSC_1049.JPG ├── README.md └── VGMPlayer ├── HighResTimer.cs ├── Program.cs ├── SerialSender.cs ├── VGMPlayer.csproj ├── VGMPlayer.sln ├── VgmFile.cs └── VgmFileHeader.cs /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | -------------------------------------------------------------------------------- /ArduinoProject/ArduinoProject.ino: -------------------------------------------------------------------------------- 1 | 2 | #define CLOCK 10 3 | #define WE 2 4 | 5 | #define PIN_D0 29 6 | #define PIN_D1 28 7 | #define PIN_D2 27 8 | #define PIN_D3 26 9 | #define PIN_D4 25 10 | #define PIN_D5 24 11 | #define PIN_D6 23 12 | #define PIN_D7 22 13 | 14 | int dataPtr = 0; 15 | int delayCounter = 0; 16 | int delayCounterTimer = 0; 17 | int delayCounterTimerMax = 4; 18 | 19 | void setup() { 20 | 21 | // This is only used if you want to attempt to let the Arduino generate the 4Mhz clock 22 | // that the SN76489 requires to operate. NOTE: Beacuse this uses a custom PWM to generate it 23 | // It MUST use pin 10 as in the clock define above. If you have a hardware 4mhz clock 24 | // device on the board however, you can leave the next line commented and not bother connecting a wire 25 | //setupFourMhzTimer(); 26 | 27 | // Set up the R/W pin 28 | pinMode(WE, OUTPUT); 29 | digitalWrite(WE, HIGH); 30 | 31 | // Set up the Data I/O pins 32 | pinMode(PIN_D0, OUTPUT); 33 | pinMode(PIN_D1, OUTPUT); 34 | pinMode(PIN_D2, OUTPUT); 35 | pinMode(PIN_D3, OUTPUT); 36 | pinMode(PIN_D4, OUTPUT); 37 | pinMode(PIN_D5, OUTPUT); 38 | pinMode(PIN_D6, OUTPUT); 39 | pinMode(PIN_D7, OUTPUT); 40 | 41 | // Set them all to logic 0 42 | digitalWrite(PIN_D0, LOW); 43 | digitalWrite(PIN_D1, LOW); 44 | digitalWrite(PIN_D2, LOW); 45 | digitalWrite(PIN_D3, LOW); 46 | digitalWrite(PIN_D4, LOW); 47 | digitalWrite(PIN_D5, LOW); 48 | digitalWrite(PIN_D6, LOW); 49 | digitalWrite(PIN_D7, LOW); 50 | 51 | // Initialise the SN76489 52 | InitiliseSoundChip(); 53 | 54 | // and the inbound default serial port 55 | Serial.begin(115200); 56 | } 57 | 58 | void setupFourMhzTimer() 59 | { 60 | // Iv'e no idea how this works :-) I grabbed it off the arduino forums 61 | // I made an attempt to understand it, but every tweak I made I killed it!!! 62 | // it works, it gives 4Mhz, so I'm just leaving it at that. 63 | pinMode(CLOCK, OUTPUT); 64 | TCCR2A = ((1 << WGM21) | (1 << COM2A0)); //0x23; 65 | TCCR2B = (1 << CS20); //0x09; 66 | 67 | OCR2A = 0x02; 68 | TIMSK2 = 0x00; 69 | 70 | OCR2B = 0x01; 71 | } 72 | 73 | void InitiliseSoundChip() 74 | { 75 | // Do the well known BOOO-BEEP BBC Initialisation sound. 76 | // Yes, it does actually serve a purpose :-) 77 | 78 | // The low tone that all BBCs play on startup (Before the higher beep all owners are used to) is actually 79 | // The default tone on channel 2 of the SN76489 sound chip. It produces this to let you know that it's working 80 | // and ready to recieve commands, and starts to produce it as soon as power is applied to the device. 81 | 82 | // We allow the default tone to be heard for half a second or so... 83 | delay(500); 84 | 85 | // Now we send the first batch of commands the BBC does on start up to the sound chip 86 | SendByteToSoundChip(0x82); // First byte of set Frequency on Tone 3 to decimal 1010 87 | SendByteToSoundChip(0x3F); // Second byte of set Frequency on Tone 3 to decimal 1010 88 | SendByteToSoundChip(0xBF); // Silence Tone Channel 2 (Volume is inverted on the beeb so 15 is silence, 0 is Loud) 89 | SendByteToSoundChip(0xA1); // First byte of set Frequency on Tone 2 to decimal 1009 90 | SendByteToSoundChip(0x3F); // Second byte of set Frequency on Tone 2 to decimal 1009 91 | SendByteToSoundChip(0xDF); // Silence Tone Channel 1 (Volume is inverted on the beeb so 15 is silence, 0 is Loud) 92 | SendByteToSoundChip(0xC0); // First byte of set Frequency on Tone 1 to decimal 1008 93 | SendByteToSoundChip(0x3F); // Second byte of set Frequency on Tone 1 to decimal 1008 94 | SendByteToSoundChip(0xFF); // Silence Noise Channel 95 | SendByteToSoundChip(0xE0); // Set noise channel to periodic noise with a low frequency 96 | 97 | // Now we wait a 50th of a second (I got these timings from the VGM file I recorded from a BBC Emulator) 98 | delay(20); 99 | 100 | // Next block of commands to the sound chip 101 | SendByteToSoundChip(0x92); // Set volume on tone channel 3 to 2 102 | SendByteToSoundChip(0x8F); // First byte of set Frequency on Tone 3 to decimal 239 103 | SendByteToSoundChip(0x0E); // Second byte of set Frequency on Tone 3 to decimal 239 104 | 105 | // Delay 15 50ths of a second (Approx 1 3rd of a sec) 106 | delay(300); 107 | 108 | // And finally the last sound chip command 109 | SendByteToSoundChip(0x9F); // Silence Tone 3 110 | } 111 | 112 | void SendByteToSoundChip(byte b) 113 | { 114 | // First split up the byte to send onto the individual I/O pins of the 115 | // chip's 8 bit data bus. 116 | digitalWrite(PIN_D0, (b&1)?HIGH:LOW); 117 | digitalWrite(PIN_D1, (b&2)?HIGH:LOW); 118 | digitalWrite(PIN_D2, (b&4)?HIGH:LOW); 119 | digitalWrite(PIN_D3, (b&8)?HIGH:LOW); 120 | digitalWrite(PIN_D4, (b&16)?HIGH:LOW); 121 | digitalWrite(PIN_D5, (b&32)?HIGH:LOW); 122 | digitalWrite(PIN_D6, (b&64)?HIGH:LOW); 123 | digitalWrite(PIN_D7, (b&128)?HIGH:LOW); 124 | 125 | // Then pulse the write enable line low for one millisecond. The R/W line on the SN76489 is inverted 126 | // that is it commonly spoken as "Read NOT Write" which essentially means that to write the data on the Dx 127 | // input pins you briefly have to pull the write line low, then return it high for normal read operation. 128 | digitalWrite(WE, LOW); 129 | delay(1); 130 | digitalWrite(WE, HIGH); 131 | 132 | } 133 | 134 | // This is the main program runtime loop fo the Arduino firmware. 135 | void loop() { 136 | 137 | // If there's anything coming down from the PC, just throw it 138 | // straight to the sound chip. No ceremony here...... 139 | if(Serial.available() > 0) 140 | { 141 | byte b = Serial.read(); 142 | SendByteToSoundChip(b); 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /ArduinoProject/test_other_timers_sn76489: -------------------------------------------------------------------------------- 1 | void setupFourMhzTimer() 2 | { 3 | // Iv'e no idea how this works :-) I grabbed it off the arduino forums 4 | // I made an attempt to understand it, but every tweak I made I killed it!!! 5 | // it works, it gives 4Mhz, so I'm just leaving it at that. 6 | pinMode(CLOCK, OUTPUT); 7 | TCCR2A = 0x23;// timer control configuration register А 8 | TCCR2B = 0x09; //timer control configuration register B 9 | OCR2A = 3; //7; //2mhz //5;//2.67mhz //4; //3.2mhz //3; //4mhz //8; //1.78mhz //I left this one - 4mhz ///comparison register A 10 | OCR2B = 1; //3; // //1; //1; //40:60 //1; //50:50 //3; //duty+46%/-54% } ///comparison register B 11 | -------------------------------------------------------------------------------- /CIrcuit Screen Grab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/CIrcuit Screen Grab.png -------------------------------------------------------------------------------- /Example VGM Tunes/13 Amps.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/13 Amps.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/1942.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/1942.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Addicts Anthem 3.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Addicts Anthem 3.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Addicts Anthem Miami Remix 89.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Addicts Anthem Miami Remix 89.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Addicts Anthem Remix.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Addicts Anthem Remix.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Addicts Anthem.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Addicts Anthem.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Blue Monday Remix.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Blue Monday Remix.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Chariots.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Chariots.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Cheers.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Cheers.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Circle in the Sand.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Circle in the Sand.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Cold Tea.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Cold Tea.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Dambuster March.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Dambuster March.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Disk User.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Disk User.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Domino Dancing.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Domino Dancing.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Dreamscape.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Dreamscape.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Enjoy the silence.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Enjoy the silence.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Equinoxe5.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Equinoxe5.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Flash Dance.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Flash Dance.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Ghostbusters.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Ghostbusters.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Golden Brown.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Golden Brown.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Good King.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Good King.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Newsong.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Newsong.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Only You.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Only You.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Pride.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Pride.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Raiders of the lost ark.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Raiders of the lost ark.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Rent.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Rent.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Sweet Dreams.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Sweet Dreams.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Thriller.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Thriller.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/True Colors.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/True Colors.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Valkeris.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Valkeris.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/Win Again.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/Win Again.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/airwolf.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/airwolf.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/ateam.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/ateam.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/dallas.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/dallas.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/doctor doctor.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/doctor doctor.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/elsewhere.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/elsewhere.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/in the mood.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/in the mood.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/its a sin.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/its a sin.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/stop.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/stop.vgm -------------------------------------------------------------------------------- /Example VGM Tunes/tubular bells.vgm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Example VGM Tunes/tubular bells.vgm -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Peter "Shawty" Shaw 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Main Circuit.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Main Circuit.bmp -------------------------------------------------------------------------------- /Main Circuit.dwg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Main Circuit.dwg -------------------------------------------------------------------------------- /Pictures/DSC_0740.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Pictures/DSC_0740.JPG -------------------------------------------------------------------------------- /Pictures/DSC_0741.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Pictures/DSC_0741.JPG -------------------------------------------------------------------------------- /Pictures/DSC_0742.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Pictures/DSC_0742.JPG -------------------------------------------------------------------------------- /Pictures/DSC_1048.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Pictures/DSC_1048.JPG -------------------------------------------------------------------------------- /Pictures/DSC_1049.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawty/sn76489arduino/855851493ffde33baeacbad3d28ba1e77153f88c/Pictures/DSC_1049.JPG -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sn76489arduino 2 | Project and Supporting files to turn an arduino into a BBC Micro music player. 3 | 4 | In this repository, you will find everything you need to create a BBC Micro Music player, using an Arduino, and a PC. 5 | 6 | I'll provide 7 | 8 | * Circuit Diagrams (To connect a Texas Instruments SN76489 *[The BBC Micro's hardware sound chip]* to your Arduino) 9 | * The code/Arduino IDE project, to run in the Arduino IDE and program into your arduino 10 | * A VGM Streamer application written in C# using dotnet core, to stream the music data from a PC to the Arduino 11 | 12 | ## A Few Notes 13 | 14 | The project was built on and designed to work with an arduino mega. It may take a little work to adapt it to work on other arduinos. 15 | 16 | The main thing you will have to do to use it on different devices will be to remap the pin numbers you use for the connections to the sound chip. Iv'e followed good arduino practice however, and provided easy to read pin number constants/defines at the top of the file, so it should be trivial with anyone who has a smattering of arduino experience to change it. 17 | 18 | MOST diagrams for the SN76489 sound chip that are available online, are taken from the original Texas Instruments data sheet. This data sheet **IS WRONG** in the original data sheet, data pin d0 was labeled as being on pin 3 of the chip, when in fact it is actually on pin 10. 19 | 20 | Once you've set the pin numbers up, build the circuit, then use the Arduino IDE, load the arduino project, and upload it to your chosen arduino device. 21 | 22 | The c# program has only been tested on a PC running under windows, however since it's dotnet core, then it should be possible to run it on ANY MACHINE that you have dot net core installed on. Dot Net core currently runs on Windows, Linux AND MacOS, instructions to install dotnet core are outside the scope of this project, the place to look for instructions for your platform is as follows: 23 | 24 | https://www.microsoft.com/net/learn/get-started/windows 25 | 26 | Once you have dotnet core installed and running correctly, use the command line of your system, change to the folder called "VGMPlayer" in the place you cloned this project, and type 27 | 28 | **dotnet run** 29 | 30 | Running the player is simple, but you'll need to change a few things first, the name of the file to play is in the file 'Program.cs' on line 69, you'll need to change this line to the file you want to play, Iv'e deliberately not made any effort to make this windows player user aware, as it's really only a proof of concept, if you know C# and you want to go changing it to ask for the file name or anything like that, then feel free to do so. 31 | 32 | In the file "SerialSender.cs", on line 10 is the name of the serial port your arduino is connected too, you will need to change this to whatever port your OS tells you your arduino is using in the arduino IDE tools menu, it will be something like "COMxx" for windows and "/dev/ttyxxxx" or "/dev/....." on linux. DOn't ask me about MacOS I don't own one, and I ain't got a clue :-) 33 | 34 | To hear the sound output, you **WILL NEED** an amplifed speaker. Personally (As you can see in the YouTube video here: https://www.youtube.com/watch?v=rOjFRLOkblk ) I use a small USB Powered Xmi mobile phone speaker, but connecting a jack plug to the line in on your PC works just as well, and gives you the added bonus that you can record it too. 35 | 36 | What ever you do, you cannot connect headphones directly to the output as it's too weak to drive them correctly so you'll not be able to hear what's being played. 37 | 38 | Once these 2 changes are correct, and you dotnet run the app, then assuming your wiring is correct, and you got the arduino programmed correctly, and you have your sound output hooked up, you should hear the dulcet tones of a genuine 1980's era BBC Sound chip playing back actual song data from an actual 80's era BBC Micro. 39 | 40 | ### How I created the VGM files...... 41 | Quite easily as it happens. 42 | 43 | Make sure you have Tom Sneddons "model-b" BBC Micro Emulator installed ( http://archive.retro-kit.co.uk/bbc.nvg.org/emulators.php3.html#Model-B ) it has to be this specific emulator as this is the only one I can find that records VGM, rather than recording the wave audio output which is what all the others do. 44 | 45 | Once you have the emulator running, click "special" on the menu bar, then "Start Recording Sound", run the program who's music you want to record and let it play util you record everything you want to record. 46 | 47 | Once done, click "special" again and "Stop recording sound", wait a few seconds and the emulator will ask you where you want to save the VGM file. 48 | 49 | Iv'e provided a bunch of VGM test files that I recorded from the emulator in the "Example VGM Tunes" folder, please be aware though that some of them are well, awfull :-) Simply beacuse some of them used some rather bizare tricks to try and get things like argeggios and complex harmonies going (Every trick in Ian Waughs excelent - "Making music on the BBC Micro Computer" book basically) and the emulator bless it's little socks did try to record things as faithfully as it could, but just didn't manage to get a good quality grab for some of them. I'm sure with patience and time I'll be able to make some better ones. 50 | 51 | You'll also find the soundtrack to my multipart BBC Model B demo "DreamScape" ( https://www.youtube.com/watch?v=_mrOAB4UBcM ) in there too, and that plays rather quite well :-) 52 | 53 | **Why a VGM file** 54 | 55 | When you record a VGM file, you physically record the instructions sent to the emulated BBC sound chip, these are the exact same instructions that the real SN76489 also understands, and obeys to make sounds. If you download a copy of the BBC Advanced User Guide, it has an entire chapter on writing these instructions on an actual BBC Micro direct to the real chip in a BBC Micro (One of the first ever 6502 Machine Code Programs I wrote on a BBC all those years ago was a direct hardware music player), the VGM file is a standard emulator format used by a number of different emulators for recording this chip level music data containing chip commands. 56 | 57 | The PC Player is in actual fact a mini VGM format file player, but it only looks at and uses the parts that are specific to the BBC Micro or SN76489 chip. The SN chip was added to the spec as it was used in a number of consoles in the 1990's, the most famous of wich was the Sega Mega Drive, so it suits our purposes perfectly. 58 | 59 | -------------------------------------------------------------------------------- /VGMPlayer/HighResTimer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | // C# High Resolution Timer Class by Ken Loveday 4 | // see: https://www.codeproject.com/Articles/98346/Microsecond-and-Millisecond-NET-Timer 5 | // for more info. 6 | 7 | namespace VGMPlayer 8 | { 9 | /// 10 | /// MicroStopwatch class 11 | /// 12 | public class MicroStopwatch : System.Diagnostics.Stopwatch 13 | { 14 | readonly double _microSecPerTick = 15 | 1000000D / System.Diagnostics.Stopwatch.Frequency; 16 | 17 | public MicroStopwatch() 18 | { 19 | if (!System.Diagnostics.Stopwatch.IsHighResolution) 20 | { 21 | throw new Exception("On this system the high-resolution " + 22 | "performance counter is not available"); 23 | } 24 | } 25 | 26 | public long ElapsedMicroseconds 27 | { 28 | get 29 | { 30 | return (long)(ElapsedTicks * _microSecPerTick); 31 | } 32 | } 33 | } 34 | 35 | /// 36 | /// MicroTimer class 37 | /// 38 | public class MicroTimer 39 | { 40 | public delegate void MicroTimerElapsedEventHandler( 41 | object sender, 42 | MicroTimerEventArgs timerEventArgs); 43 | public event MicroTimerElapsedEventHandler MicroTimerElapsed; 44 | 45 | System.Threading.Thread _threadTimer = null; 46 | long _ignoreEventIfLateBy = long.MaxValue; 47 | long _timerIntervalInMicroSec = 0; 48 | bool _stopTimer = true; 49 | 50 | public MicroTimer() 51 | { 52 | } 53 | 54 | public MicroTimer(long timerIntervalInMicroseconds) 55 | { 56 | Interval = timerIntervalInMicroseconds; 57 | } 58 | 59 | public long Interval 60 | { 61 | get 62 | { 63 | return System.Threading.Interlocked.Read( 64 | ref _timerIntervalInMicroSec); 65 | } 66 | set 67 | { 68 | System.Threading.Interlocked.Exchange( 69 | ref _timerIntervalInMicroSec, value); 70 | } 71 | } 72 | 73 | public long IgnoreEventIfLateBy 74 | { 75 | get 76 | { 77 | return System.Threading.Interlocked.Read( 78 | ref _ignoreEventIfLateBy); 79 | } 80 | set 81 | { 82 | System.Threading.Interlocked.Exchange( 83 | ref _ignoreEventIfLateBy, value <= 0 ? long.MaxValue : value); 84 | } 85 | } 86 | 87 | public bool Enabled 88 | { 89 | set 90 | { 91 | if (value) 92 | { 93 | Start(); 94 | } 95 | else 96 | { 97 | Stop(); 98 | } 99 | } 100 | get 101 | { 102 | return (_threadTimer != null && _threadTimer.IsAlive); 103 | } 104 | } 105 | 106 | public void Start() 107 | { 108 | if (Enabled || Interval <= 0) 109 | { 110 | return; 111 | } 112 | 113 | _stopTimer = false; 114 | 115 | System.Threading.ThreadStart threadStart = delegate () 116 | { 117 | NotificationTimer(ref _timerIntervalInMicroSec, 118 | ref _ignoreEventIfLateBy, 119 | ref _stopTimer); 120 | }; 121 | 122 | _threadTimer = new System.Threading.Thread(threadStart); 123 | _threadTimer.Priority = System.Threading.ThreadPriority.Highest; 124 | _threadTimer.Start(); 125 | } 126 | 127 | public void Stop() 128 | { 129 | _stopTimer = true; 130 | } 131 | 132 | public void StopAndWait() 133 | { 134 | StopAndWait(System.Threading.Timeout.Infinite); 135 | } 136 | 137 | public bool StopAndWait(int timeoutInMilliSec) 138 | { 139 | _stopTimer = true; 140 | 141 | if (!Enabled || _threadTimer.ManagedThreadId == 142 | System.Threading.Thread.CurrentThread.ManagedThreadId) 143 | { 144 | return true; 145 | } 146 | 147 | return _threadTimer.Join(timeoutInMilliSec); 148 | } 149 | 150 | public void Abort() 151 | { 152 | _stopTimer = true; 153 | 154 | if (Enabled) 155 | { 156 | _threadTimer.Abort(); 157 | } 158 | } 159 | 160 | void NotificationTimer(ref long timerIntervalInMicroSec, 161 | ref long ignoreEventIfLateBy, 162 | ref bool stopTimer) 163 | { 164 | int timerCount = 0; 165 | long nextNotification = 0; 166 | 167 | MicroStopwatch microStopwatch = new MicroStopwatch(); 168 | microStopwatch.Start(); 169 | 170 | while (!stopTimer) 171 | { 172 | long callbackFunctionExecutionTime = 173 | microStopwatch.ElapsedMicroseconds - nextNotification; 174 | 175 | long timerIntervalInMicroSecCurrent = 176 | System.Threading.Interlocked.Read(ref timerIntervalInMicroSec); 177 | long ignoreEventIfLateByCurrent = 178 | System.Threading.Interlocked.Read(ref ignoreEventIfLateBy); 179 | 180 | nextNotification += timerIntervalInMicroSecCurrent; 181 | timerCount++; 182 | long elapsedMicroseconds = 0; 183 | 184 | while ((elapsedMicroseconds = microStopwatch.ElapsedMicroseconds) 185 | < nextNotification) 186 | { 187 | System.Threading.Thread.SpinWait(10); 188 | } 189 | 190 | long timerLateBy = elapsedMicroseconds - nextNotification; 191 | 192 | if (timerLateBy >= ignoreEventIfLateByCurrent) 193 | { 194 | continue; 195 | } 196 | 197 | MicroTimerEventArgs microTimerEventArgs = 198 | new MicroTimerEventArgs(timerCount, 199 | elapsedMicroseconds, 200 | timerLateBy, 201 | callbackFunctionExecutionTime); 202 | MicroTimerElapsed(this, microTimerEventArgs); 203 | } 204 | 205 | microStopwatch.Stop(); 206 | } 207 | } 208 | 209 | /// 210 | /// MicroTimer Event Argument class 211 | /// 212 | public class MicroTimerEventArgs : EventArgs 213 | { 214 | // Simple counter, number times timed event (callback function) executed 215 | public int TimerCount { get; private set; } 216 | 217 | // Time when timed event was called since timer started 218 | public long ElapsedMicroseconds { get; private set; } 219 | 220 | // How late the timer was compared to when it should have been called 221 | public long TimerLateBy { get; private set; } 222 | 223 | // Time it took to execute previous call to callback function (OnTimedEvent) 224 | public long CallbackFunctionExecutionTime { get; private set; } 225 | 226 | public MicroTimerEventArgs(int timerCount, 227 | long elapsedMicroseconds, 228 | long timerLateBy, 229 | long callbackFunctionExecutionTime) 230 | { 231 | TimerCount = timerCount; 232 | ElapsedMicroseconds = elapsedMicroseconds; 233 | TimerLateBy = timerLateBy; 234 | CallbackFunctionExecutionTime = callbackFunctionExecutionTime; 235 | } 236 | 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /VGMPlayer/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | // VGM Player written by 5 | // !Shawty!/DS in 2017 6 | // NOTE: This player is designed only to playback BBC Model B VGM Files 7 | 8 | namespace VGMPlayer 9 | { 10 | class Program 11 | { 12 | static VgmFile vgmFile = new VgmFile(); 13 | static MicroTimer timer; 14 | static bool displayRunning = false; 15 | 16 | static void DisplayThread() 17 | { 18 | Console.Clear(); 19 | while (displayRunning) 20 | { 21 | Console.SetCursorPosition(0, 0); 22 | Console.Write("BBC Micro VGM Player (Serial Port Arduino Version)"); 23 | 24 | Console.SetCursorPosition(0, 2); 25 | Console.Write("Delay {0} ", vgmFile.DelayCounter); 26 | 27 | Console.SetCursorPosition(0, 3); 28 | Console.Write("Last Byte Sent: 0x{0:X} ", vgmFile.LastByteSent); 29 | 30 | Console.SetCursorPosition(0, 4); 31 | Console.Write("Tone 3 Volume: {0}".PadRight(60, ' '), vgmFile.Tone3Volume); 32 | 33 | Console.SetCursorPosition(0, 5); 34 | Console.Write("Tone 2 Volume: {0}".PadRight(60, ' '), vgmFile.Tone2Volume); 35 | 36 | Console.SetCursorPosition(0, 6); 37 | Console.Write("Tone 1 Volume: {0}".PadRight(60, ' '), vgmFile.Tone1Volume); 38 | 39 | Console.SetCursorPosition(0, 7); 40 | Console.Write(" Noise Volume: {0}".PadRight(60, ' '), vgmFile.NoiseVolume); 41 | 42 | Console.SetCursorPosition(0, 9); 43 | Console.WriteLine("3 {0}".PadRight(60, ' '), new String('*', vgmFile.Tone3Bar)); 44 | 45 | Console.SetCursorPosition(0, 10); 46 | Console.WriteLine("2 {0}".PadRight(60, ' '), new String('*', vgmFile.Tone2Bar)); 47 | 48 | Console.SetCursorPosition(0, 11); 49 | Console.WriteLine("1 {0}".PadRight(60, ' '), new String('*', vgmFile.Tone1Bar)); 50 | } 51 | Console.SetCursorPosition(0, 14); 52 | Console.WriteLine("Song Finished, Please Press Return to Exit"); 53 | } 54 | 55 | static void CallPlayer(object stateInfo, MicroTimerEventArgs e) 56 | { 57 | if(vgmFile.SongLooping) 58 | { 59 | displayRunning = false; 60 | timer.Enabled = false; 61 | timer.Stop(); 62 | } 63 | 64 | vgmFile.PlayNext(); 65 | } 66 | 67 | static void Main(string[] args) 68 | { 69 | vgmFile.Load(@"C:\BBC Micro Emulation\Embeded Stuff\dreamscape.vgm"); // CHANGE THIS TO THE FILE YOU WANT TO PLAY 70 | 71 | timer = new MicroTimer(); 72 | timer.MicroTimerElapsed += new MicroTimer.MicroTimerElapsedEventHandler(CallPlayer); 73 | timer.Interval = 22; // NOTE: This is 22 MICROSECONDS, NOT MILLISECONDS as is usually the case in windows/dotnet 74 | timer.Enabled = true; 75 | 76 | Thread songInfoThread = new Thread(new ThreadStart(DisplayThread)); 77 | displayRunning = true; 78 | songInfoThread.Start(); 79 | 80 | Console.SetCursorPosition(0, 13); 81 | Console.WriteLine("Press return to exit."); 82 | 83 | // Wait for return to be pressed..... 84 | Console.ReadLine(); 85 | displayRunning = false; 86 | timer.Enabled = false; 87 | timer.Stop(); 88 | 89 | } 90 | 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /VGMPlayer/SerialSender.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO.Ports; 3 | 4 | namespace VGMPlayer 5 | { 6 | public class SerialSender: IDisposable 7 | { 8 | private SerialPort _serialPort = new SerialPort(); 9 | 10 | private string _portName = "COM11"; // Change this to the com port your arduino is connected too 11 | private int _baudRate = 115200; 12 | 13 | public SerialSender() 14 | { 15 | _serialPort.PortName = _portName; 16 | _serialPort.BaudRate = _baudRate; 17 | _serialPort.DataBits = 8; 18 | _serialPort.Parity = Parity.None; 19 | _serialPort.StopBits = StopBits.One; 20 | _serialPort.Open(); 21 | } 22 | 23 | public void Send(byte b) 24 | { 25 | byte[] buf = new byte[1]; 26 | buf[0] = b; 27 | _serialPort.Write(buf, 0, 1); 28 | } 29 | 30 | public void Dispose() 31 | { 32 | if(_serialPort.IsOpen) 33 | { 34 | _serialPort.Close(); 35 | } 36 | } 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /VGMPlayer/VGMPlayer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /VGMPlayer/VGMPlayer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2010 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VGMPlayer", "VGMPlayer.csproj", "{EB6E23BC-60B7-4FA3-AA53-67DBB6AF4103}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {EB6E23BC-60B7-4FA3-AA53-67DBB6AF4103}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {EB6E23BC-60B7-4FA3-AA53-67DBB6AF4103}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {EB6E23BC-60B7-4FA3-AA53-67DBB6AF4103}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {EB6E23BC-60B7-4FA3-AA53-67DBB6AF4103}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {008C4A21-7E50-4FA3-B9D0-302CC9818B4E} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /VGMPlayer/VgmFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | 6 | // VGM Player written by 7 | // !Shawty!/DS in 2017 8 | // NOTE: This player is designed only to playback BBC Model B VGM Files 9 | 10 | namespace VGMPlayer 11 | { 12 | public class VgmFile 13 | { 14 | // This class is based on the 1.50 VGM File format 15 | 16 | private SerialSender _serialSender = new SerialSender(); 17 | 18 | private VgmFileHeader _header = new VgmFileHeader(); 19 | private byte[] _chipData; 20 | 21 | // The player process is quite simple. 22 | // All we care about are VGM Commands 0x61, 0x63, 0x50 & 0x66 23 | // 61 means set a delay, if we see one of these we set the delay counter to that value, then each time the player is called 24 | // we decrement that delay until it reaches 0, we do not allow the vgm data reader to progress to the next byte if this 25 | // delay counter is not 0. Command 63 is a shortcut for a delay of 1/50th of a second 26 | // Command 50, means the next byte is to be sent directly to the sound chip, so thats exactly what we do :-) 27 | // Command 66, means end of song data, so we set the variable to show that it's looped, then reset the song data pointer to 0 28 | 29 | private int _dataPointer = 0; // Current position in our chip data array 30 | private int _delayCounter = 0; // Counts down to 0, and is set by a delay command in the chip data 31 | //private int _delayCounterTimer = 0; // Used to control how fast the delay counter counts down 32 | //private int _delayCounterTimerMax = 500; // Used to set the speed on the delay counter timer. Higher is slower 33 | // NOTE: You WILL need to fiddle with the above max value to get the correct playback speed, remember the PC you 34 | // run this in is likley to be orders of magnitude faster than the 16Mhz arduino recieving the data to send to 35 | // the sound chip, which itself is only clocked at 4Mhz. 36 | // The serial connection in the Arduino code is set at 115200, and the Arduino's inbound buffer is only 128 bytes 37 | // so timing will need to be adjusted!!! 38 | 39 | private byte barMax = 32; // Maximum size a bar value can be 40 | private byte barSpeed = 0; // Counter to track bar speed 41 | private byte barSpeedMax = 192; // How many itterations of the play loop before we reduce a bar value; 42 | 43 | // These 3 vars are public (That is they can be accessed outside the player class) and are read only. 44 | public bool SongLooping { get; private set; } 45 | public int DelayCounter { get { return _delayCounter; } } 46 | public byte LastByteSent { get; private set; } 47 | 48 | public byte Tone3Volume { get; private set; } 49 | public byte Tone2Volume { get; private set; } 50 | public byte Tone1Volume { get; private set; } 51 | public byte NoiseVolume { get; private set; } 52 | 53 | public byte Tone3Bar { get; private set; } 54 | public byte Tone2Bar { get; private set; } 55 | public byte Tone1Bar { get; private set; } 56 | 57 | public void Load(string fileName) 58 | { 59 | Stream fileStream = new FileStream(fileName, FileMode.Open); 60 | BinaryReader reader = new BinaryReader(fileStream); 61 | 62 | LoadHeader(reader); 63 | LoadChipData(reader); 64 | 65 | fileStream.Close(); 66 | 67 | SongLooping = false; 68 | } 69 | 70 | public void PlayNext() 71 | { 72 | // Plays the next available entry in the chip data array, this is designed to be called repeatedly 73 | // from a loop outside the class, so each call of it will decode one command only. 74 | 75 | // Adjust our tone counters 76 | if (barSpeed == 0) 77 | { 78 | Tone3Bar--; if (Tone3Bar < 1) { Tone3Bar = 1; }; if (Tone3Bar > barMax) { Tone3Bar = barMax; } 79 | Tone2Bar--; if (Tone2Bar < 1) { Tone2Bar = 1; }; if (Tone2Bar > barMax) { Tone2Bar = barMax; } 80 | Tone1Bar--; if (Tone1Bar < 1) { Tone1Bar = 1; }; if (Tone1Bar > barMax) { Tone1Bar = barMax; } 81 | barSpeed = barSpeedMax; 82 | } 83 | else 84 | { 85 | barSpeed--; 86 | } 87 | 88 | // If we have a delay set 89 | if (_delayCounter > 0) 90 | { 91 | // THIS WAS THE OLD WAY OF DOING THE TIMING UNTIL I MANAGED TO USE A HIGH RES TIMER 92 | // IVE NOT TRIED THIS ON ANYTHING OTHER THAN WINDOWS 7 YET, SO MAY HAVE TO Go 93 | // BACK TO USING THIS ON OTHER PLATFORMS 94 | // Increment our delay timer 95 | //_delayCounterTimer++; 96 | // And if it's hit our max..... 97 | //if(_delayCounterTimer > _delayCounterTimerMax) 98 | //{ 99 | // Decrement the actual delay and reset the timer 100 | _delayCounter--; 101 | //_delayCounterTimer = 0; 102 | //} 103 | return; 104 | } 105 | 106 | // if our delayCounter is 0 then we get here, ready to decode the next command 107 | 108 | byte currentDataByte = _chipData[_dataPointer]; 109 | 110 | // Now we decode our VGM chip data commands. 111 | // In the case of the SN76489 in the BBC not all the commands are used, so we decode 112 | // only the one we are interested in. 113 | 114 | switch (currentDataByte) 115 | { 116 | case 0x61: 117 | // Set a delay counter value, low byte is in the next byte, high in the one following that 118 | int delayVal = (_chipData[_dataPointer + 2] << 8) + _chipData[_dataPointer + 1]; 119 | _delayCounter = delayVal; 120 | _dataPointer = _dataPointer + 3; // Increase pointer to next data entry 121 | 122 | //Console.WriteLine("Set Delay {0}", _delayCounter); 123 | break; 124 | 125 | case 0x63: 126 | // Short cut for a 1/50th of a second delay 127 | _delayCounter = 0x7203; // See VGM spec for these values (These are 20ms on a PAL System running at 4Mhz) 128 | _dataPointer++; // Increase pointer to next data entry; 129 | 130 | //Console.WriteLine("Set 1/50th Second Delay"); 131 | break; 132 | 133 | case 0x50: 134 | // This is an actual raw byte to send to the sound chip 135 | byte chipByte = _chipData[_dataPointer + 1]; 136 | _dataPointer = _dataPointer + 2; // Increase pointer to the next entry 137 | 138 | _serialSender.Send(chipByte); 139 | 140 | // Extract some usefull info from the command 141 | // Where not going to do frequencies or anything like that, beacuse they are 2 byte 142 | // we risk slowing down the high presicion send loop too much. 143 | 144 | // Volumes however are simple. 145 | if ((chipByte & 0x90) == 0x90) 146 | { 147 | Tone3Volume = (byte)(chipByte & 0x0F); 148 | } 149 | 150 | if ((chipByte & 0xB0) == 0xB0) 151 | { 152 | Tone2Volume = (byte)(chipByte & 0x0F); 153 | } 154 | 155 | if ((chipByte & 0xD0) == 0xD0) 156 | { 157 | Tone1Volume = (byte)(chipByte & 0x0F); 158 | } 159 | 160 | if ((chipByte & 0xF0) == 0xF0) 161 | { 162 | NoiseVolume = (byte)(chipByte & 0x0F); 163 | } 164 | 165 | // and we can at least do some simple counters, that we set to max 166 | // when a frequency on a channel is triggered, and count down 167 | // so while not frequency related, we can at least do a simple 168 | // kind of VU :-) 169 | 170 | if ((chipByte & 0x80) == 0x80) 171 | { 172 | Tone3Bar = barMax; 173 | } 174 | 175 | if ((chipByte & 0xA0) == 0xA0) 176 | { 177 | Tone2Bar = barMax; 178 | } 179 | 180 | if ((chipByte & 0xC0) == 0xC0) 181 | { 182 | Tone1Bar = barMax; 183 | } 184 | 185 | //Console.WriteLine("Send Chip Byte {0}", chipByte); 186 | LastByteSent = chipByte; 187 | break; 188 | 189 | case 0x66: 190 | // End of current song data 191 | _dataPointer = 0; // Reset and loop our song 192 | SongLooping = true; // Set this so that calling program knows if song is looping 193 | 194 | //Console.WriteLine("Reset Data Pointer"); 195 | break; 196 | 197 | default: 198 | // This shouldn't be needed, well at least the data pointer check shouldn't be needed anyway 199 | // beacuse in theory we should always hit a 0x66 at the end. However if the 66 is not found, then 200 | // this stops a program crash due to trying to access data outside the array. 201 | // The increment is still needed however, as that allows us to skip over VGM commands that we don't yet implement. 202 | _dataPointer++; 203 | if(_dataPointer > (_chipData.Length - 1)) 204 | { 205 | _dataPointer = 0; 206 | } 207 | 208 | //Console.WriteLine("Unknown VGM Command {0}", currentDataByte); 209 | break; 210 | } 211 | } 212 | 213 | private void LoadHeader(BinaryReader reader) 214 | { 215 | byte[] magicBytes = reader.ReadBytes(4); 216 | string magic = Encoding.Default.GetString(magicBytes).Trim(); 217 | 218 | if(magic != "Vgm") 219 | { 220 | throw new ApplicationException("Specifed file is NOT a VGM file"); 221 | } 222 | 223 | _header.VgmMagic = magic; 224 | 225 | _header.EofOffset = reader.ReadUInt32() + 4; // Eof val is 4 bytes in, so the actual offset is this + 4 226 | _header.Version = reader.ReadUInt32(); 227 | _header.Sn76489Clock = reader.ReadUInt32(); 228 | _header.Ym2413Clock = reader.ReadUInt32(); 229 | _header.Gd3Offset = reader.ReadUInt32(); 230 | _header.TotalSamples = reader.ReadUInt32(); 231 | _header.LoopOffset = reader.ReadUInt32(); 232 | _header.LoopOffset = reader.ReadUInt32(); 233 | _header.Rate = reader.ReadUInt32(); 234 | _header.SnFb = reader.ReadUInt16(); 235 | _header.Snw = reader.ReadByte(); 236 | _header.Reserved = reader.ReadByte(); 237 | _header.Ym2612Clock = reader.ReadUInt32(); 238 | _header.Ym2151Clock = reader.ReadUInt32(); 239 | 240 | // From this point in the file, the following offset + the current file pointer is the ACTUAL location of the chip data 241 | // so we need to work out and record what that location actually is. 242 | // In most cases it will be 0x40 for v1.50 and prior, but it CAN be different, esp in the V1.50 VGM spec 243 | // yes this is a messy way of doing it, and if the data position is greater than 32 bits then where screwed 244 | // however, 32 bits tends to suggest a 4gb file, and Iv'e not seen one break the 10mb barrier yet, so I think where 245 | // fairly safe. :-) 246 | long currentFilePointer = reader.BaseStream.Position; 247 | _header.VgmDataOffset = reader.ReadUInt32(); 248 | if (_header.VgmDataOffset == 0) 249 | { 250 | // No offset was specified, so we default to 0x40 which is the known start offset. 251 | _header.VgmDataOffset = 0x40; 252 | } 253 | else 254 | { 255 | // There is a positive offset, so lets take the file pos, add that on and store that. 256 | _header.VgmDataOffset = (uint)((int)currentFilePointer + _header.VgmDataOffset); 257 | } 258 | 259 | // We don't use the last 2 reserved words in the header 260 | var reserved = reader.ReadUInt32(); 261 | reserved = reader.ReadUInt32(); 262 | 263 | } 264 | 265 | private void LoadChipData(BinaryReader reader) 266 | { 267 | List result = new List(); 268 | 269 | // Move the file stream to the start of our data 270 | reader.BaseStream.Seek(_header.VgmDataOffset, SeekOrigin.Begin); 271 | 272 | var dataSize = _header.EofOffset - _header.VgmDataOffset; 273 | 274 | result.AddRange(reader.ReadBytes((int)dataSize)); 275 | _chipData = result.ToArray(); 276 | } 277 | 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /VGMPlayer/VgmFileHeader.cs: -------------------------------------------------------------------------------- 1 | namespace VGMPlayer 2 | { 3 | public class VgmFileHeader 4 | { 5 | public string VgmMagic { get; set; } 6 | public uint EofOffset { get; set; } 7 | public uint Version { get; set; } 8 | public uint Sn76489Clock { get; set; } 9 | public uint Ym2413Clock { get; set; } 10 | public uint Gd3Offset { get; set; } 11 | public uint TotalSamples { get; set; } 12 | public uint LoopOffset { get; set; } 13 | public uint LoopSamples { get; set; } 14 | public uint Rate { get; set; } 15 | public ushort SnFb { get; set; } 16 | public byte Snw { get; set; } 17 | public byte Reserved { get; set; } 18 | public uint Ym2612Clock { get; set; } 19 | public uint Ym2151Clock { get; set; } 20 | public uint VgmDataOffset { get; set; } 21 | 22 | } 23 | } 24 | --------------------------------------------------------------------------------