Do want to play music on your propeller? (Propeller chiptune player)
Ahle2
Posts: 1,179
Minimum requirements to run this software:
* You own a propeller board with a SD card
* You have a NES game pad or a keyboard connected to your propeller
* You have a PAL or NTSC display connected to your propeller
* You used to be (is) a C64 or an Atari ST owner (or Amstrad, MSX, Spectrum)
* You like music from these machines
* You are a nostalgic person
If you and your hardware meets the minimum requirements, please go ahead and try it out.
Instructions:
1. Unpack all files from the archive named "MixedChiptunes.zip" to a SD card.
2. Unpack "PropellerChipTunePlayer.zip"
3. Edit the pin configuration in the "con section" of "PropellerChipTunePlayer.spin" to suit your needs
(If you want to use a NES game pad, uncomment the "InputNES.spin" and edit the "con section" in that file)
4. Compile, upload and run the top object (PropellerChipTunePlayer.spin)
5. Enjoy
Keyboard controlls:
Space - Fast forward
Enter - Pause
Right arrow - next tune
Left arrow - prvious tune
Down arrow - next page (tune += 10)
Up arrow - previous page (tune -= 10)
NES controlls:
A button - Fast forward
Start button - Pause
Right arrow - next tune
Left arrow - prvious tune
Down arrow - next page (tune += 10)
Up arrow - previous page (tune -= 10)
Color coding:
Green background: AY38910/YM2149F sound chip (Atari ST, Amstrad CPC, MSX, Spectrum 128)
Blue background: MOS8580 sound chip (Commodore 64)
* You own a propeller board with a SD card
* You have a NES game pad or a keyboard connected to your propeller
* You have a PAL or NTSC display connected to your propeller
* You used to be (is) a C64 or an Atari ST owner (or Amstrad, MSX, Spectrum)
* You like music from these machines
* You are a nostalgic person
If you and your hardware meets the minimum requirements, please go ahead and try it out.
Instructions:
1. Unpack all files from the archive named "MixedChiptunes.zip" to a SD card.
2. Unpack "PropellerChipTunePlayer.zip"
3. Edit the pin configuration in the "con section" of "PropellerChipTunePlayer.spin" to suit your needs
(If you want to use a NES game pad, uncomment the "InputNES.spin" and edit the "con section" in that file)
4. Compile, upload and run the top object (PropellerChipTunePlayer.spin)
5. Enjoy
Keyboard controlls:
Space - Fast forward
Enter - Pause
Right arrow - next tune
Left arrow - prvious tune
Down arrow - next page (tune += 10)
Up arrow - previous page (tune -= 10)
NES controlls:
A button - Fast forward
Start button - Pause
Right arrow - next tune
Left arrow - prvious tune
Down arrow - next page (tune += 10)
Up arrow - previous page (tune -= 10)
Color coding:
Green background: AY38910/YM2149F sound chip (Atari ST, Amstrad CPC, MSX, Spectrum 128)
Blue background: MOS8580 sound chip (Commodore 64)


Comments
Suggestion,
1) You should make it ignore any file extension that it doesn't recognize.
2) Have the program start playing immediately for those who don't have
a NES connected. (Example: This special)
OBC
Are you able to get it to work on the Hybrid running at 96 Mhz?
@Oldbitcollector (Jeff)
Good suggestions. I will fix these things in the next release.
@Cluso99
If you meet the minimum requirements, it shouldn't take more than a couple of minutes to test it out.
@jazzed
Thanks Jazzed !
What exactly are you working on? Or is it a secret?
Yes with the necessary adjustments, I love it!!
Was running a bit fast till I noticed you hard coded a delay to 80_000_000, I set it to clkfreq and it was sweet! :cool:
Thanks,
Coley
Audio unfortunately is monaural. I just don't have enough Prop pins or room for another chip.
Bug fixes:
- Changed the "wait routine" to use clkfreq instead of a hardcoded value
- YM tunes dumped from machines with lower than 2 Mhz clock freq now plays at the right pitch
- Fixed so only valid files can be played
- Plus at lot of small bug fixes
C3_PropellerChipTunePlayer - Archive [Date 2011.05.03 Time 23.53].zip
PropellerChipTunePlayer - Archive [Date 2011.05.03 Time 22.54].zip
This $%^&**&&^^$#@!@#$#$% tv....
Seems I have to learn fast and make a VGA replacements for all these several different tv drivers... Still can hear the old, beautiful sound of these beautiful machines
VGA replacements are sometimes possible, but not always. TV has lower sweep frequencies, which means better tricks in the video pixel render loops. The color space is different too, though I don't see that as too big of a deal.
Not that I'm advocating you don't knock up replacements. Many users would be pleased to have those. TV is cool though, I must say I love it because I can put a Propeller display on my laptop using a capture device. Great way to run a Prop! A serial terminal is a close second...
VGA more or less means you've got to be somewhere around a screen. Why they don't make laptops with a basic VGA in, is beyond me. Would buy one in a second! Less even.
It was a piece of cake for CRT type monitors to be designed to handle the lower frequencies but nope it almost never happened, and was called a multi-frequency monitor when it did allow it. Not that your average VGA capable monitor wasn't multi-frequency anyway.
It's even simpler now with built-in scan converters inside all modern LCD monitors. The minimum horizontal scan rate of 30kHz is a totally arbitrary limit. Sad really.
I received my Quickstart board last week and have been very impressed with all the Propeller chiptune emulators. My current experience is with a Midibox SID that I just finished -- uses a real SID chip
--tom
We can play these emulators with midi fairly simply. It takes very little code using objects in the obex, especially the Midi In object. I did a quick script to play sidcog from midi in. I can post code if interested. SidSynth is a very nice synth that will work on your quickstart with a simple midi in.
I've been porting the chiptune player over to my hardware. The inital port is working if a bit buggy. I need to finish the UI and then I'll upload a video to youtube.
The code would be great. I will have a booth at our local Maker Faire next weekend :
http://makerfairenc.com
and I'd like to have the Propeller synth there....to give the Propeller platform some exposure. It would be really cool to have multiple emulators running in different cogs, all controllable via MIDI. Perhaps a simple interface to map keyboards to cogs and it would be an great piece of gear for live performance. I'm still figuring this all out...I will have more time to do more experimenting after Maker Faire is over. At this point, I want to make sure I have finished projects to show.
Is there a standard MIDI (in/out) library, or is everyone doing their own thing? A standard library for common stuff like MIDI would be great.
It's funny, I discovered all this Propeller music stuff after happening upon one of your posts in the Midibx forum. Otherwise I would not even have thought to look over here.
Thanks,
Tom
I've incorporated the original version of your code in my PropellerBASIC, (I'm counting every byte or I would have used the current one.)
I'd love to add MIDI translation to it.
OBC
CON {''Hardware version 3.4.1 - see diagram. '' } _clkmode = xtal1 + pll16x ' use crystal x 16 _xinfreq = 5_000_000 'with 5mhz crystal doNoteOn = $00000001 ' noteOn for midi in object doNoteOff = $00000002 ' NoteOff for midi in object doAftertouch = $00000004 ' doController = $00000008 doProgramChange = $00000010 doChannelPressure = $00000020 doPitchWheel = $00000040 doSysex = $00000100 doMTC = $00000200 doSongPosPtr = $00000400 doSongSelect = $00000800 doTuneRequest = $00001000 doMidiClock = $00002000 doMidiTick = $00004000 doMidiStart = $00008000 doMidiContinue = $00010000 doMidiStop = $00020000 doActiveSense = $00040000 doReset = $00080000 MidiInMode = doNoteOn+doNoteOff ' set midi mode to Note On and note off only LeftPin = 24 '<-- Audio Out pins RightPin = 27 VAR OBJ wait : "timing" midi: "MidiIn" sid : "SidCog" PUB SidMidi | sco, channel, note, velocity, event, NotesPlayed[3] Midi.Start(26,MidiInMode) 'Start Midi In object in cog 'Set sid patch here: SID.start(rightPin, leftPin) 'Start the emulated SID chip in one cog SID.resetRegisters 'Reset all SID registers SID.setVolume(15) 'Set volume to max 'wait.pause1s(1) SID.setWaveform(0, SID#SQUARE) 'Set waveform type on channel1 to square wave SID.setPulseWidth(0, 1928) 'Set the pulse width on channel1 to 47:53 SID.setADSR(0, 2, 9, 5, 4) 'Set Envelope on channel1 SID.setWaveform(1, SID#NOISE) 'Set waveform type on channel2 to noise (drum sound) SID.setADSR(1, 0, 8, 4, 7) 'Set Envelope on channel2 SID.setWaveform(2, SID#SAW) 'Set waveform type on channel3 to saw wave SID.setADSR(2, 7, 4, 8, 9) 'Set Envelope on channel3 'Lp Bp Hp SID.setFilterType(true, false, false) 'Enable lowpass filter 'Ch1 Ch2 Ch3 SID.setFilterMask(true, false, false) 'Enable filter on the "bass channel" (channel1) SID.setResonance(15) 'Set the resonance value to max SID.setCutoff(287) 'Set cutoff repeat sco from 0 to 2 ' Turn off all notes SID.noteOff(sco) REPEAT 'Start main loop here event := Midi.evt 'get midi event note := event 'and copy it channel := note 'to the variables 'Velocity := note 'describing the event event := (event & $FF00_0000) >> 24 ' mask event type channel := (channel & $00FF_0000) >> 16 ' mask channel number note := (note & $0000_FF00) >> 8 ' mask not number ' Velocity &= $0000_00FF 'mask velocity, not used if event == 1 'if note off SID.noteOff(channel) ' send note off to sid on that channel elseif event == 0 ' check for Note on SID.noteOn(channel, note2freq(note)) 'and send note on to sid on channel, convert note number to frequency PUB note2freq(note) | octave octave := note/12 note -= octave*12 return (noteTable[note]>>(8-octave)) DAT noteTable word 16350, 17320, 18350, 19450, 20600, 21830, 23120, 24500, 25960, 27500, 29140, 30870midi in object here : http://obex.parallax.com/objects/229/*edit*
I just commented this code to make for easier reading. I will start a post dedicated to PERFORMING music on the prop next week!
It never crossed my mind that I would have to be concerned with SD card compatibility or different SD card drivers. I guess I'll go look in the code. I am using a plug in micro SD adapter from Gadget Gangster.
--tom
So I used Kye's sd driver in my vga player.
Maybe the simplest way is to add some functions to this driver with the same names, as in fsrw, then replace fsrw with Kye's driver without changing rest of player's code.
CON ENABLE_PAL = false OBJ SID : "SIDcog" AY : "AYcog" ''***************Start chiptune Player VAR here VAR 'byte LineOfText2[25] 'byte LineOfText1[18] byte playing byte chipTuneType byte prevChipTuneType byte ffw word playRate long wait long ct_fn long nrOfTunes long scrollSpeed long frameCounter long scrollCounter long textPointer long AY_register PUB ChiptuneStart | i, oldStatus, fin,yval, xval RamErase SDBMPtoRam(string("Trans2.bmp")) sid.start(_rightChannelAudioPin, _leftChannelAudioPin) getNrOfTunes Clearscreen(0) ' clear screen to black - uses the display driver above curx :=0 cury :=0 SetHeaderMessage(@message4) frameCounter := 0 ct_fn := 1 chipTuneType := 1 playRate := 50 scrollSpeed := 2 playing := false ffw := false wait := cnt DrawBMPRam(0,0,280) 'Main loop repeat ' Wait between register updates if (cnt - wait) > 0 if ffw wait := cnt + (clkfreq/(playRate<<2)) else wait := cnt + (clkfreq/playRate) ' Update the PSG if playing case chipTuneType 1: updateSID 2: updateAY ' Check for a change in tv status ' if oldStatus <> tv_status ' oldStatus := tv_status ' ' Check for vbl ' if tv_status == 2 ' scroll ' ' ' Handle user interactions ' if input.getLastState <> input.getCurrentState SelectSPIGroup yval := TouchYPercent ' decode yval 0-100% xval := TouchXPercent ' decode xval 0-100% if (xval <> 255) and (yval <> 255) 'SelectMemGroup 'hex(xval,2) ' if display any debug messages need to restart the SPI cog as it has been stopped by the debug display 'hex(yval,2) if (yval < 20) fat.changeDirectory(string("..")) RamErase ' erase ram chip fat LoadDesktop ' a image 240x320 for the desktop. Load first as there is a ramclear routine that does not delete the first entry DrawDesktop LoadIcons ' load the mask and icons onto the screen ILISetcursor(0,0) ' cursor for debugging to 0,0 SelectSPIGroup ' selects this group and starts the cog return if (yval > 89) SelectMemGroup ' Handle previous tune if (xval < 19) handleMuting if ct_fn == 0 ct_fn := nrOfTunes + 1 ct_fn-- openNextFile setScrollMessage(@songName +6) ' Handle pausing if (xval > 37 and xval < 70) playing := not playing handleMuting ifnot playing setScrollMessage(@message2) else ffw := false setScrollMessage(@songName +6) ' Handle next tune if (xval > 79 and xval < 99) handleMuting if ct_fn == nrOfTunes ct_fn := -1 ct_fn++ openNextFile setScrollMessage(@songName + 6) PRI openNextFile | i, t,x, nameP ffw := false playing := true ' Iterate through files and find the file with fileNumber == X fat.closeFile repeat i from 0 to ct_fn fat.listEntries("N") nameP := fat.listname t:=0 repeat until byte [nameP][t] == 0 byte [@LineOfText1][t]:= byte[nameP][t] t++ byte[@LineOfText1][t] := 0 prevChipTuneType := chipTuneType chipTuneType := 0 t := 0 x := 0 repeat strsize(@directory) byte[@LineOfText3][x] := byte [@directory][x] x ++ repeat strsize(@LineOfText1) byte[@LineOfText3][x] := byte [@LineOfText1][t] x ++ t ++ fat.openFile(@LineOfText3, "R") ' Find out what kind of file type we are dealing with if fat.readData(@LineOfText2, 4) < 0 if byte[nameP - 3] == "D" and byte[nameP - 2] == "M" and byte[nameP - 1] == "P" chipTuneType := 1 if LineOfText2[0] == "S" and LineOfText2[1] == "D" and LineOfText2[2] == "M" and LineOfText2[3] == "P" ' SDMP chipTuneType := 1 elseif LineOfText2[0] == "Y" and LineOfText2[1] == "M" and LineOfText2[2] == "6" and LineOfText2[3] == "!" ' YM6! chipTuneType := 2 else fat.closeFile AY.stop SID.stop setScrollMessage(@message1) return ' Set some standard values in case the header doesn't provide any info playRate := 50 bytefill(@songName + 10, 32, 15) intToString(@songName + 6, ct_fn, 5) repeat i from 0 to 15 if LineOfText1[i] byte[@songName + i + 10] := LineOfText1[i] else i := 16 ' Init everything, read the header and start the PSG fat.openFile(@LineOfText1, "R") if chipTuneType == 1 readHeaderSID initSID elseif chipTuneType == 2 readHeaderAY initAY wait := cnt + 20_000_000 PRI handleMuting case chipTuneType 1: if playing SID.unmute else SID.mute 2: if playing AY.unmute else AY.mute PRI initSID | registers if prevChipTuneType == 2 AY.stop elseif prevChipTuneType == 1 SID.stop registers := SID.start( _rightChannelAudioPin, _leftChannelAudioPin ) SID.resetRegisters ' OSC.stop ' OSC.start( display_base, registers + 28, @tv_status, @scrollSpeed ) chipTuneType := 1 ' setSIDColor PRI initAY | registers if prevChipTuneType == 2 AY.stop elseif prevChipTuneType == 1 SID.stop registers := AY.start(_rightChannelAudioPin, _leftChannelAudioPin, AY_register) AY.resetRegisters ' OSC.stop ' OSC.start( display_base, registers + 16, @tv_status, @scrollSpeed ) chipTuneType := 2 ' setAYColor PRI updateSID if( fat.readData(@LineOfText2, 25) ) < 0 ct_fn++ openNextFile SID.updateRegisters(@LineOfText2) PRI updateAY if( fat.readData(@LineOfText2, 16) ) < 0 ct_fn++ openNextFile AY.updateRegisters(@LineOfText2) PRI readHeaderSID | i, t if( fat.readData(@LineOfText2, 25) ) => 0 if LineOfText2[0] == "S" and LineOfText2[1] == "D" and LineOfText2[2] == "M" and LineOfText2[3] == "P" playRate := LineOfText2[4] | (LineOfText2[5]<<8) repeat i from 0 to 15 t := LineOfText2[ i + 8 ] if t byte[ @songName + i + 10 ] := t else byte[ @songName + i + 10 ] := 32 PRI readHeaderAY | extra, digiDrums, i, t if( fat.readData(@LineOfText2, 12)) => 0 ' 00 - read "YM6!" header ' 04 - read "LeOnArD!" identifier if( fat.readData(@LineOfText2, 4) ) => 0 ' 0c - number of VBL frames (play rate) if( fat.readData(@LineOfText2, 4) ) => 0 ' 10 - attributes if( fat.readData(@LineOfText2, 2) ) => 0 ' 14 - number of digi drum samples digiDrums := LineOfText2[0] | (LineOfText2[1]<<8) if( fat.readData(@LineOfText2, 4) ) => 0 ' 16 - YM2149 external frequency in Hz AY_register := LineOfText2[3] | (LineOfText2[2]<<8) | (LineOfText2[1] << 16) | (LineOfText2[0] << 24) if( fat.readData(@LineOfText2, 2) ) => 0 ' 1a - player frequency in Hz playRate := LineOfText2[1] | (LineOfText2[0]<<8) if( fat.readData(@LineOfText2, 4) ) => 0 ' 1c - frame where looping starts if( fat.readData(@LineOfText2, 2) ) => 0 ' 20 - extra bytes following (should be 0) extra := LineOfText2[0] | (LineOfText2[1]<<8) bytefill(@LineOfText2, 32, 15) if get_string ' read song name repeat i from 0 to 14 t := LineOfText2[i] if t byte[ @songName + i + 10 ] := t else byte[ @songName + i + 10 ] := 32 get_string ' read author name get_string ' read comments / software PRI get_string | n, t repeat n from 0 to 127 if (t := fat.readByte ) =< 0 return n else if n < 25 LineOfText2[n] := t var byte firstFile[18] PRI getNrOfTunes | t,i nrOfTunes := -1 fat.closefile 'propfont_string( ) fat.changeDirectory(string("tunes")) repeat until strcomp(@Dir2, t) fat.listEntries("N") t := fat.listname 'propfont_String(t) fat.listEntries("N") t := fat.listname 'propfont_String(t) i := 0 repeat until byte[t][i] == 0 firstFile[i] := byte[t] [i] ' propfont_out(".") i++ crlf 'propfont_string(string("FirstFile")) 'propfont_string(@firstFile) t:= -1 repeat until strcomp(@firstFile, t) fat.listEntries("N") t := fat.listname ' propfont_String(t) nrOfTunes++ if nrOfTunes < 1 nrOfTunes -= 2--tom