Zwiebelchen
Hosted Project GR
- Joined
- Sep 17, 2009
- Messages
- 7,234
What is this?
Basicly, it's a guitar tab interpreter system for Warcraft III, allowing to import very few highly compressed sounds and generate music with them, kind of like a MIDI interpreter software. It was designed to be used in combination with Guitar Pro, but is also compatible to any other midi interpreter software that allows exporting midis or files as a standardized guitar tab.
How to use it?
Best would be to check the demo map. There is also a quick tutorial at the end of this post.
---------------------------------
Tutorial on how to import tabs to this system (for Guitar Pro users):
Sorry that I could not provide you with screenshots of the english version of Guitar Pro. But I think the screens might still be helpful.
(1) First, open your midi or .gp file with Guitar Pro. You can download a free trial version of Guitar Pro 6 here:
http://www.guitar-pro.com/en/index.php?pg=download
(2) Select the track you want to extract.
(3) In Guitar Pro 5, click on "Tools", then "Fill up bars with breaks".
(4) Click on your track, check the tuning information for the octave numbers.
(5) Go to "Data" and "Export" and select "Export as Ascii"
(6) A window opens. Set the number of columns to 999 --> 1.
Check your guitar tab for triplet (|-3-| or 3) and accent (>) information. --> 2
If your tab contains those extra lines, make sure to copy them.
(7) Copy and paste everything into a text converted trigger in your WC3 trigger editor.
(8) Create Instruments and import sounds by using the provided struct methods (check example), then create your Track, add your instrument to it. --> 1
Add extra sounds to your instruments if chord notes are needed. --> 2
In case your tab provides triplet information, make sure to copy and paste them too. --> 3
Make sure everything is on the same line: The length information, the triplet information, the last digit of your note value. --> 4
Make sure to push a line of spacebars to other track parts even if there are no triplets on this part. If you use triplets just for one part, you need to put it over every part of this track aswell. --> 5
Adjust the tuning information of the first part by adding the octave number (see step 4). Make sure to add a spacebar to your length, accent and triplet line for this part, to make up for the extra character. --> 6
(9) Add the track to your Song. Repeat this for all tracks.
(10) Use the play method to play your song. Enjoy.
Basicly, it's a guitar tab interpreter system for Warcraft III, allowing to import very few highly compressed sounds and generate music with them, kind of like a MIDI interpreter software. It was designed to be used in combination with Guitar Pro, but is also compatible to any other midi interpreter software that allows exporting midis or files as a standardized guitar tab.
How to use it?
Best would be to check the demo map. There is also a quick tutorial at the end of this post.
JASS:
/*
=======================================================================================
TAB READER v1.1 Created by Zwiebelchen
=======================================================================================
Scans Guitar Pro Sheets and turns them into audible music - without importing whole music files.
Inspired by Midi-Readers
Check Hiveworkshop Forums for a Tutorial on how to use it.
SUPPORTS:
- Tabs extracted by Guitar Pro (tested with GP5, but should also work with other versions)
- Notes up to an interval of 1/32, such as triplets and dotted notes
- Accented notes (>) and ghost notes (bracket notes)
- Tied notes (L)
- Percussion Tabs
- Customizable tunings (Drop D, Bass tunings, etc.)
...and more to come.
LIMITATIONS:
- Warcraft III comes with some limitations when using sounds. This system is build to try to overcome most of those limitations, but for some, this is simply not possible and
needs the user to react according to them:
(1) sounds of the same filepath can only be played up to 4 times at the same time
(2) sounds of the same filepath must have a delay of 0.1 seconds inbetween them to be played correctly
(3) only 16 sounds (no matter which filepath) can be played at the same time
- rules (1) and (2) can be bypassed by adding more sound files to your instrument. Check the API on how to do so.
- rule (3) can not be avoided, however. Its up to the user to create music that doesnt play too many sounds at the same time
NOTE: fading sounds also take up one of those 16 slots. If you got a lot of trouble with this limitation, set your fadeout rates higher.
Check your song for instruments that dont need fading at all (most likely rhythm guitars or basses) and set the fadeout rate to 12700,
to be allowed to play more sounds at the same time
examples:
- in order to allow for chords (starting the same sound multiple times on different pitches), you need to register more sounds to your instrument.
- the system uses a trick to bypass the 0.1 second delay rule on very fast passages. However, this means that the last sound might be cut off early, so if very fast passages sound weird
you should consider adding one more sound to your instrument.
API:
Struct Song:
[static].create(string name, real bpm, real duration) returns thistype
name: allows to put in a name string in case you need something to be displayed on screen
bpm: the speed of the song in beats per minute (= quarters per minute)
duration: length of the song in seconds; information required for looping
.addTrack(Track tr)
tr: the Track struct you want to assign to the song. You can change the maximum number of tracks possible in the system globals
.play(player for)
for: player the song shall be played for.
.stop(player for)
for: player the song shall be stopped for.
.isPlaying(player for) returns boolean
for: player you want to check wether the song is playing or not.
.name [readonly string]
Allows to get the name of the song
Struct Track:
[static].create(Instrument inst, integer vol, integer fadeout) returns thistype
inst: assigns an instrument to the Track. Only one instrument can be assigned to the same trick, but you can assign one instrument to multiple tracks if you wish (see LIMITATIONS!)
vol: the volume of your track between 0 and 127. The accent informations are multiplied by that number to get the real volume of a note.
Ordinary notes use volume*0.85, ghost notes use volume*0.5, accented notes use volume*1
fadeout: Sets the fadeout rate of the instrument sounds (0-12700).
This setting is important for defining the style of your instrument. If your fadeout rate is very slow (the lower the number, the slower your fade), the sounds will
get blurred and overlap each other, even without using tied notes (L). This is nice for Piano type sounds with high percussive momentum and high reverb.
If your fadeout rate is very high, you can achieve a staccato kind of sound, suited for rhythm instruments. Also, if your fadeout is faster,
the instrument will consume less sound slots, which is good if you got trouble with the sound limit of Warcraft III.
(see LIMITATIONS for more information)
recommended settings: instant cutoff (staccato): no cutoff (auto tied notes):
fadeout: 15-30 fadeout: 12700 fadeout: 0
[static].createPercussion(integer vol) returns thistype
creates a percussion track. Remember that having more than one percussion track usually doesnt make much sense, so better put all your stuff in one track to improve performance.
vol: the volume of your track between 0 and 127. The accent informations are multiplied by that number to get the real volume of a note.
Ordinary notes use volume*0.85, ghost notes use volume*0.5, accented notes use volume*1
[static].registerPercussionSound(integer which, string path)
registers a sound to all percussive tracks. You can only put one sound per ID.
which: the midi-ID of your percussion sound (see table at the end of this documentation!)
path: filepath of your sound
.pushTabLength(string s) [required!]
pushes the ascii string for note length information into your track. If you push multiple times, you are able to divide your track into multiple track parts,
in case you reach the maximum string length. The length strings must be pushed in the order of playback. All strings of the same track part
must have the same length. Dont worry: The system will display a warning you if you did something wrong here.
.pushTabTriplet(string s) [optional]
pushes the ascii string for triplet information into your track. Follows the same rules as .pushTabLength().
If only one later track part of your track has triplet information, you still need to push the empty triplet part strings that come first, even if they only contain spacebars.
They still need to have the same string length or you will receive a warning.
.pushTabAccent(string s) [optional]
pushes the ascii string for accent information into your track. Follows the same rules as .pushTabLength().
If only one later track part of your track has accent information, you still need to push the empty accent part strings that come first, even if they only contain spacebars.
They still need to have the same string length or you will receive a warning.
.pushTabNotes[integer line, string s) [optional]
line: determines which line of the track your string is. Guitars usually have lines 0 to 5, with 0 being the highest and 5 being the lowest string.
pushes the ascii string for note values into your track. Follows the same rules as .pushTabLength(). In case a whole line is empty (for example, if your tab only using the
thinest two strings), you can leave it out. So only put those lines here that actually contain information. However, if you put a line, you will need to push it for all track parts,
even if it only contains empty lines. Otherwise you will receive a warning.
The first push per line needs the tune information of your tab line. ASCII outputs of guitar pro usually dont display the octave number here, so you need to add that.
In this case, dont forget to add an extra spacebar on the first track part of your length, triplet and accent information lines to match the increased length.
Standard guitar tuning is: E3, A3, D4, G4, B4, E5. Standard bass tuning is: E2, A2, D3, G3.
If you did everything right, the length information should always be on the second digit of your note. If it is on the first digit, you need to put in a spacebar.
HINT: use indendation to improve readability!
example:
.pushTabTriplet( " |-3-|-3-| ")
.pushTabLength( " Q Q Q ")
.pushTabNotes(0, "E5||--10----------")
.pushTabNotes(1, "B4||--------------")
.pushTabNotes(2, "G4||--------------")
.pushTabNotes(3, "D4||--------------")
.pushTabNotes(4, "A3||---0---3---2--")
.pushTabNotes(5, "E3||--------------")
For percussion Tabs, there is no alteration needed, as there is no tuning information. Simply leave everything as it is.
.volume [integer vol]
allows to change the volume of your track at any time.
Struct Instrument:
[static].create(string key, string filepath) returns thistype
key: the key of the sound you imported, used for determining pitch values of other notes
must contain: base note (A, B, C, D, E, F, G), half note information (# or b) and octave number (1 to 9).
example: "C#3" or "D4" - "E3" being the standard tune of the thickest string on guitars, "E5" being the standard tune of the thinnest string.
remember: in music theory, octave numbers switch at the C note, not at the note A!
filepath: the path of your sound file.
.add(string key, string filepath)
See create method. Allows to add more sounds to one instrument, to allow for chords and multiple notes at the same time (see LIMITATIONS!)
You can change the maximum number of sounds allowed per instrument in globals.
------------------------------------------------------------------------------------------------------------------------
PERCUSSION ID TABLE:
27 High Q
28 Slap
29 Scratch Push
30 Scratch Pull
31 Sticks
32 Square Click
33 Metronome Click
34 Metronome Bell
35 Bass Drum 2
36 Bass Drum 1
37 Side Stick/Rimshot
38 Snare Drum 1
39 Hand Clap
40 Snare Drum 2
41 Low Tom 2
42 Closed Hi-hat
43 Low Tom 1
44 Pedal Hi-hat
45 Mid Tom 2
46 Open Hi-hat
47 Mid Tom 1
48 High Tom 2
49 Crash Cymbal 1
50 High Tom 1
51 Ride Cymbal 1
52 Chinese Cymbal
53 Ride Bell
54 Tambourine
55 Splash Cymbal
56 Cowbell
57 Crash Cymbal 2
58 Vibra Slap
59 Ride Cymbal 2
60 High Bongo
61 Low Bongo
62 Mute High Conga
63 Open High Conga
64 Low Conga
65 High Timbale
66 Low Timbale
67 High Agogô
68 Low Agogô
69 Cabasa
70 Maracas
71 Short Whistle
72 Long Whistle
73 Short Güiro
74 Long Güiro
75 Claves
76 High Wood Block
77 Low Wood Block
78 Mute Cuíca
79 Open Cuíca
80 Mute Triangle
81 Open Triangle
82 Shaker
83 Jingle Bell
84 Bell Tree
85 Castinets
86 Mude Surdo
87 Open Surdo
======================================================================================================
*/
library TabReader uses TimerUtils
globals
//configurables
private constant integer FILE_TIME_OFFSET = 50 //when saving sound files, most programs add a very small break before the sounds to avoid clipping noise.
//This constant determines when the actual sound starts in your sound files to avoid delay [ms] (default: 50)
private constant integer PARTS_PER_TRACK = 5 //max number of strings per tabline, as strings are limited to 2000 characters
private constant integer TABLINES_PER_TRACK = 6 //max number of tablines per track
private constant integer PARTSTIMESLINES = 30 //must be set to PARTS_PER_TRACK*TABLINES_PER_TRACK (due to struct limitations, this can not be automated)
private constant integer TRACKS_PER_SONG = 12 //max number of tracks per song; remember that you are limited to 16 sounds playing at the same time
private constant integer SOUNDS_PER_INSTRUMENT = 6 //max number of sound files that can be assigned to one instrument
private constant integer SOUNDSTIMESFOUR = 24 //must be set to SOUNDS_PER_INSTRUMENT*4 (due to struct limitations, this can not be automated)
private constant boolean ALLOW_32TH = true //Allows the use of 1/32th notes. Those notes can still be dotted or played as triplets.
//set this to FALSE to limit the system to 1/16th notes, resulting in higher performance.
//end of configurables
//-------------------------------------
private constant real PBASE = 1.059465 //base used for determining pitch values
//pitch value = PBASE^n
//with n = number of half steps to desired note
private constant real IGNORE_SAME_INTERVAL = 0.1 //the interval in which Warcraft III ignores StartSound() calls of the same filepath
private constant integer MIN_BPM = 40
private constant integer MAX_BPM = 180
private sound array drumsounds[61] //in midi standard, there are 61 different drum sounds
endglobals
private function CharToKey takes string char returns integer
if char == "C" then
return 0
elseif char == "D" then
return 2
elseif char == "E" then
return 4
elseif char == "F" then
return 5
elseif char == "G" then
return 7
elseif char == "A" then
return 9
elseif char == "B" then
return 11
endif
return (-1)
endfunction
private function TranslateKey takes string s returns integer
//first 3 chars of tab strings are tab keys
//C0 being the lowest note possible, B9 being the highest
local string temp = SubString(s, 0, 1)
local integer octave = 0
local integer key = CharToKey(temp)
set temp = SubString(s, 1, 2)
if temp == "#" then //sharp keys
set key = key+1
set temp = SubString(s, 2, 3)
set octave = S2I(temp)
if octave != 0 or temp == "0" then //octave number
set key = key+octave*12
endif
elseif temp == "b" then //flat keys for compat
set key = key-1
set temp = SubString(s, 2, 3)
set octave = S2I(temp)
if octave != 0 or temp == "0" then //octave number
set key = key+octave*12
endif
else
set octave = S2I(temp)
if octave != 0 or temp == "0" then //octave number
set key = key+octave*12
endif
endif
if key < 0 then
return 0
elseif key > 119 then
return 119
endif
return key
endfunction
struct Instrument
private integer count
private integer fOut
private integer array note[SOUNDS_PER_INSTRUMENT]
private sound array obj[SOUNDSTIMESFOUR]
private real array lp[SOUNDSTIMESFOUR]
private real array creationtime[SOUNDSTIMESFOUR]
real array stopWhen[SOUNDSTIMESFOUR]
static method create takes string key, string filepath, integer fadeout returns thistype
//key like: "C#2" or "D4"
local thistype this = thistype.allocate()
set this.fOut = fadeout
//create sound objects to allow playing the same sound up to 4 times (this is the internal hardcoded limitation of warcraft III, so we won't need more than that)
set this.obj[0] = CreateSound(filepath, false, false, false, 10, fOut, "CombatSoundsEAX")
set this.obj[1] = CreateSound(filepath, false, false, false, 10, fOut, "CombatSoundsEAX")
set this.obj[2] = CreateSound(filepath, false, false, false, 10, fOut, "CombatSoundsEAX")
set this.obj[3] = CreateSound(filepath, false, false, false, 10, fOut, "CombatSoundsEAX")
set this.lp[0] = 1
set this.lp[1] = 1
set this.lp[2] = 1
set this.lp[3] = 1
set this.note[0] = TranslateKey(key)
set this.count = 1
return this
endmethod
method add takes string key, string path returns nothing
//allows to add up more sounds with a different filepath to your instrument
//there are 4 rules you need to know about this:
//*1 for pitching, the system will always try to find the closest registered note sound from those first
//*2 you HAVE TO add one new filepath for every note STARTED at the same time. For example, if your song involves 3-string chords, you need to import 2 more sound files to make it work (chord notes)
//*3 the same file can be run up to 4 times simoultanously, if the notes that triggered it are not started at the same time (overlapping notes, i.e. for guitar or piano arpeggios).
//you can also use this to add a little diversity to the sounds, like adding extra treble to high sounds, to simulate strumming of thinner strings
//key like: "C#2" or "D4"
if count < SOUNDS_PER_INSTRUMENT then
//create sound objects to allow playing the same sound up to 4 times (this is the internal hardcoded limitation of warcraft III, so we won't need more than that)
set obj[count*4+0] = CreateSound(path, false, false, false, 10, fOut, "CombatSoundsEAX")
set obj[count*4+1] = CreateSound(path, false, false, false, 10, fOut, "CombatSoundsEAX")
set obj[count*4+2] = CreateSound(path, false, false, false, 10, fOut, "CombatSoundsEAX")
set obj[count*4+3] = CreateSound(path, false, false, false, 10, fOut, "CombatSoundsEAX")
set lp[count*4+0] = 1
set lp[count*4+1] = 1
set lp[count*4+2] = 1
set lp[count*4+3] = 1
set note[count] = TranslateKey(key)
set count = count+1
endif
endmethod
private method setPitch takes integer i, real pitch returns nothing
//due to the bugged pitch native, we need this workaround snippet in order to make it work
if GetSoundIsPlaying(obj[i]) or GetSoundIsLoading(obj[i]) then
call SetSoundPitch(obj[i], 1/lp[i])
call SetSoundPitch(obj[i], pitch)
set lp[i] = pitch
else
if pitch == 1 then
call SetSoundPitch(obj[i], 1.0001)
set lp[i] = 1.0001
else
call SetSoundPitch(obj[i], pitch)
set lp[i] = pitch
endif
endif
endmethod
method resetTimestamps takes nothing returns nothing
local integer i = 0
loop
exitwhen i >= (count*4)
set creationtime[i] = 0
if obj[i] != null then
if GetSoundIsPlaying(obj[i]) or GetSoundIsLoading(obj[i]) then
call StopSound(obj[i], false, true)
endif
endif
set i=i+1
endloop
endmethod
method stopNote takes integer objkey, real timestamp returns nothing
if objkey >= 0 then
if stopWhen[objkey] <= timestamp then
if fOut == 12700 then //non fading sound
call StopSound(obj[objkey], false, false)
else
call StopSound(obj[objkey], false, true)
endif
endif
endif
endmethod
method playNote takes integer key, integer vol, real timestamp returns integer //this function is called inside a local block for performance reasons, so do not create/alter/destroy handles
local integer dist = 1000
local integer newdist
local integer closest = 0
local integer closesub = 0
local integer oldest = 0
local integer oldsub = 0
local real oldtime = 0
local integer i = 0
local integer j
local sound new = null
local sound old = null
//these loops seem to be overkill, but as the exits are very early (>3), it's not as performance hungry as it might look
loop
exitwhen i >= count
set newdist = IAbsBJ(note[i]-key)
set j = 0
loop //check wether the sound's filepath is valid to be played, as Warcraft III doesn't allow playing the same filepath twice in a short interval
exitwhen j>3
if timestamp-creationtime[i*4+j] < IGNORE_SAME_INTERVAL then
exitwhen true //sound is not valid, so check next filepath
endif
set j = j+1
endloop
if j>3 then //sound is valid, so we can perform additional checks
if newdist < dist then //first check if the sound is closer to the desired key than the last one found
set j = 0
loop //now check for an unused sound
exitwhen j>3
if not (GetSoundIsPlaying(obj[i*4+j]) or GetSoundIsLoading(obj[i*4+j])) then //found an unused sound, so make this sound and filepath the new reference
set dist = newdist
set closest = i
set closesub = j
set new = obj[i*4+j]
exitwhen true
else //keep the playing sound in mind in case we can find no other sound
if creationtime[i*4+j] < oldtime or old == null then
set oldest = i
set oldsub = j
set old = obj[i*4+j]
set oldtime = creationtime[i*4+j]
endif
endif
set j = j+1
endloop
endif
else
if i == 0 then //store the first not valid sound in case we can find nothing else
if oldtime == 0 then
loop
exitwhen j>3
if GetSoundIsPlaying(obj[i*4+j]) or GetSoundIsLoading(obj[i*4+j]) then //find a USED sound, as only those can be played again even between the 0.1 interval and keep it as a reserve
set oldest = i
set oldsub = j
set oldtime = creationtime[i*4+j]
//don't put the sound object into the old variable yet, as it is part of the improved reserve condition - this one is only for extreme emergencies
exitwhen true
endif
set j = j+1
endloop
endif
endif
endif
set i = i + 1
endloop
if new != null then
set dist = key-note[closest]
call setPitch(closest*4+closesub, Pow(PBASE, dist))
call SetSoundVolume(new, vol)
call StartSound(new)
call SetSoundPlayPosition(new, FILE_TIME_OFFSET)
set creationtime[closest*4+closesub] = timestamp
set old = null
set new = null
return (closest*4+closesub)
else //no valid sound could be found, so abuse a playing sound
if old == null then //not even a reserve has been found, so use the emergency reserve
set old = obj[oldest*4+oldsub]
endif
set dist = key-note[oldest]
call setPitch(oldest*4+oldsub, Pow(PBASE, dist))
call SetSoundVolume(old, vol)
call StartSound(old) //overwrites stop command in case the sound was currently fading out
call SetSoundPlayPosition(old, FILE_TIME_OFFSET)
set creationtime[oldest*4+oldsub] = timestamp
set old = null
return (oldest*4+oldsub)
endif
endmethod
endstruct
struct Track
integer volume
private Instrument instrument
private boolean perc
private string array accent[PARTS_PER_TRACK]
private string array triplet[PARTS_PER_TRACK]
private string array length[PARTS_PER_TRACK]
private string array tab[PARTSTIMESLINES]
private integer array s[TABLINES_PER_TRACK]
private real ttn = 0
private real tracktimestamp
private integer position = 0
private boolean plays = false
private integer array checklength[PARTS_PER_TRACK]
private integer accentcount = 0
private integer tripletcount = 0
private integer lengthcount = 0
private integer array tabcount[TABLINES_PER_TRACK]
private integer array key[TABLINES_PER_TRACK]
static method create takes Instrument inst, integer vol returns thistype
local thistype this = thistype.allocate()
if vol > 127 then
set this.volume = 127
elseif vol < 0 then
set this.volume = 0
else
set this.volume = vol
endif
set this.instrument = inst
set this.perc = false
return this
endmethod
static method createPercussion takes integer vol returns thistype
local thistype this = thistype.allocate()
if vol > 127 then
set this.volume = 127
elseif vol < 0 then
set this.volume = 0
else
set this.volume = vol
endif
set this.perc = true
return this
endmethod
static method registerPercussionSound takes integer which, string path returns nothing
if which >= 27 and which <= 87 then
if drumsounds[which-27] != null then
debug call BJDebugMsg("ERROR: Percussion Sound ID already assigned.")
endif
set drumsounds[which-27] = CreateSound(path, false, false, false, 12700, 12700, "CombatSoundsEAX")
else
debug call BJDebugMsg("ERROR: Invalid percussion ID.")
endif
endmethod
private static method playPercussion takes integer which, integer volume returns nothing
local integer i
if which >= 27 and which <= 87 then
set i = which-27
call StartSound(drumsounds[i])
call SetSoundPlayPosition(drumsounds[i], FILE_TIME_OFFSET)
call SetSoundVolume(drumsounds[i], volume)
endif
endmethod
method pushTabAccent takes string st returns nothing
if accentcount < PARTS_PER_TRACK then
if StringLength(st) == 0 then
debug call BJDebugMsg("ERROR: Wrong line input!")
return
endif
if checklength[accentcount] == StringLength(st) or checklength[accentcount] == 0 then
set checklength[accentcount] = StringLength(st)
set accent[accentcount] = st
set accentcount = accentcount + 1
else
debug call BJDebugMsg("ERROR: Wrong line input!")
endif
endif
endmethod
method pushTabTriplet takes string st returns nothing
if tripletcount < PARTS_PER_TRACK then
if StringLength(st) == 0 then
debug call BJDebugMsg("ERROR: Wrong line input!")
return
endif
if checklength[tripletcount] == StringLength(st) or checklength[tripletcount] == 0 then
set checklength[tripletcount] = StringLength(st)
set triplet[tripletcount] = st
set tripletcount = tripletcount + 1
else
debug call BJDebugMsg("ERROR: Wrong line input!")
endif
endif
endmethod
method pushTabLength takes string st returns nothing
if lengthcount < PARTS_PER_TRACK then
if StringLength(st) == 0 then
debug call BJDebugMsg("ERROR: Wrong line input!")
return
endif
if checklength[lengthcount] == StringLength(st) or checklength[lengthcount] == 0 then
set checklength[lengthcount] = StringLength(st)
set length[lengthcount] = st
set lengthcount = lengthcount + 1
else
debug call BJDebugMsg("ERROR: Wrong line input!")
endif
endif
endmethod
method pushTabNotes takes integer line, string st returns nothing
if line < TABLINES_PER_TRACK then
if StringLength(st) == 0 then
debug call BJDebugMsg("ERROR: Wrong line input!")
return
endif
if tabcount[line] < PARTS_PER_TRACK then
if checklength[tabcount[line]] == StringLength(st) or checklength[tabcount[line]] == 0 then
set checklength[tabcount[line]] = StringLength(st)
set tab[line*PARTS_PER_TRACK+tabcount[line]] = st
//in case input is first input, store the key of the tabline
if perc then
set key[line] = 0
else
if tabcount[line] == 0 then
set key[line] = TranslateKey(st)
endif
endif
set tabcount[line] = tabcount[line] + 1
endif
else
debug call BJDebugMsg("ERROR: Wrong line input!")
endif
endif
endmethod
method stopAll takes nothing returns nothing
if not perc then
call instrument.resetTimestamps()
endif
endmethod
method read takes real interval, boolean reset returns nothing
local integer i = 0
local integer j
local integer part
local integer k
local integer m
local real volfactor
local string temp
set tracktimestamp = tracktimestamp+interval //does not interfere with other players, as the read function is already within a local block at this point
if reset then
set position = 3
set ttn = 0
set plays = true
set tracktimestamp = 1
loop
exitwhen i >= TABLINES_PER_TRACK
set s[i]=-1
set i=i+1
endloop
if not perc then
call instrument.resetTimestamps()
endif
else
set ttn = ttn-interval
endif
if ttn <= 0 and plays then //only check for new information when last note ended
set j = 0
set part = R2I(position/2048)//determines the string part we are looking at
set i = position-part*2048
set k = StringLength(length[part])-2
set m = 0
set volfactor = 0.85 //standard volume factor
loop
set i=i+1
set temp = SubString(length[part],i,i+1)
if temp != " " and temp != "." then
if temp == "T" then //1/32th
static if ALLOW_32TH then
set ttn = interval*3
else
return 0
endif
elseif temp == "S" then //1/16th
set ttn = interval*6
elseif temp == "E" then //1/8th
set ttn = interval*12
elseif temp == "Q" then //quarter
set ttn = interval*24
elseif temp == "H" then //half
set ttn = interval*48
elseif temp == "W" then //whole
set ttn = interval*96
else
set ttn = 0
endif
static if not ALLOW_32TH then
set ttn = ttn/2
endif
if SubString(length[part],i+1,i+2) == "." then //dotted note
set ttn = ttn*1.5
endif
if tripletcount > 0 then
if part < tripletcount then
if SubString(triplet[part],i,i+1) != " " then
set ttn = ttn*0.666 //triplet note
endif
endif
endif
set ttn = ttn-0.001 //in case of rounding errors, subtract a very small amount of ttn to allow for the <= 0 check to return true
//now that we have the length information, we need to check for accent information
if accentcount > 0 then
if part < accentcount then
if SubString(accent[part],i,i+1) == ">" then
set volfactor = 1 //accentuation mark applies to the notes of all tablines, so doing this before scanning the notes is correct
endif
endif
endif
//now we can finally scan for the actual notes
loop
exitwhen j >= TABLINES_PER_TRACK
if tabcount[j] <= 0 then //in case there is no more tab on this line (like 4 or 5 string basses)
exitwhen true
endif
set temp = SubString(tab[j*PARTS_PER_TRACK+part],i,i+1)
if temp != "-" then
//there is information on this tabline
set m = S2I(temp)
if m > 0 or temp == "0" then
if not perc then
call instrument.stopNote(s[j], tracktimestamp)
set s[j] = -1
endif
//check previous Substring for 2-digit notes and add tabline key for true note value
set m = m+10*S2I(SubString(tab[j*PARTS_PER_TRACK+part],i-1,i))+key[j]
if SubString(tab[j*PARTS_PER_TRACK+part],i+1,i+2) == ")" then //ghost note
set volfactor = volfactor*0.5
endif
if perc then
call playPercussion(m, R2I(I2R(volume)*volfactor))
else
set s[j] = instrument.playNote(m, R2I(I2R(volume)*volfactor), tracktimestamp)
set instrument.stopWhen[s[j]] = tracktimestamp+ttn
endif
elseif temp == "L" then //only keep the sound going when the note is tied (L)
if not perc then
set instrument.stopWhen[s[j]] = tracktimestamp+ttn
endif
else
if not perc then
call instrument.stopNote(s[j], tracktimestamp)
set s[j] = -1
endif
endif
else
if not perc then
call instrument.stopNote(s[j], tracktimestamp)
set s[j] = -1
endif
endif
set j = j+1
endloop
exitwhen true
endif
if i>=k then //reached end of part and did not play anything yet
set part = part+1
if part < lengthcount then //not the last part of the track so ...
set position = part*2048 //... go to next part ...
call read(interval, false) //... instantly!
return
else
set j = 0
loop
exitwhen j >= TABLINES_PER_TRACK
set s[j] = -1
set plays = false
call stopAll()
set j = j+1
endloop
endif
exitwhen true
endif
endloop
set position = i+part*2048
endif
endmethod
endstruct
struct Song
readonly string name
private real interval
private real duration
private real timestamp
private real array endtime[12]
private boolean array looping[12]
private boolean array playing[12]
private boolean isrepeating
private timer reader
private integer trackcount
private Track array t[TRACKS_PER_SONG]
static method create takes string nam, real bpm, real dur returns thistype
local thistype this = thistype.allocate()
set this.name = nam
set this.duration = dur
if bpm < MIN_BPM then
set this.interval = 1/(MIN_BPM*0.2)
elseif bpm > MAX_BPM then
set this.interval = 1/(MAX_BPM*0.2)
else
set this.interval = 1/(bpm*0.2) //bpm/60*8*3
endif
static if ALLOW_32TH then
set this.interval = this.interval/2
endif
set this.trackcount = 0
set this.reader = NewTimer()
set this.timestamp = 1
return this
endmethod
method addTrack takes Track tr returns nothing
if trackcount < TRACKS_PER_SONG then
set t[trackcount] = tr
set trackcount = trackcount + 1
endif
endmethod
method stop takes player for returns nothing
local integer i = 0
local integer j = 0
local boolean b = true
set playing[GetPlayerId(for)] = false
loop
exitwhen i > 11
if playing[i] then
set b = false
else
//stop all sounds locally
if GetLocalPlayer() == Player(i) then
set j = 0
loop
exitwhen j >= trackcount
call t[j].stopAll()
set j=j+1
endloop
endif
endif
set i = i + 1
endloop
if b then
//halt timer when nobody is listening
call PauseTimer(reader)
set isrepeating = false
endif
endmethod
private method reset takes nothing returns nothing
local integer j = 0
loop
exitwhen j >= trackcount
call t[j].read(interval, true)
set j = j+1
endloop
endmethod
private static method periodic takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local integer i = 0
local integer j = 0
set timestamp = timestamp+interval
loop
exitwhen i > 11
if playing[i] then
if timestamp > endtime[i] then
if looping[i] then
set endtime[i]=timestamp+duration
if GetLocalPlayer() == Player(i) then
call reset()
endif
else
call stop(Player(i))
endif
endif
endif
set i = i + 1
endloop
if playing[GetPlayerId(GetLocalPlayer())] then //everything after this is local
set j = 0
loop
exitwhen j >= trackcount
call t[j].read(interval, false)
set j = j+1
endloop
endif
endmethod
method play takes player for, boolean loopit returns nothing
set playing[GetPlayerId(for)] = true
set endtime[GetPlayerId(for)] = timestamp+duration
set looping[GetPlayerId(for)] = loopit
if GetLocalPlayer() == for then
call reset()
endif
if not isrepeating then
call SetTimerData(reader, this)
call TimerStart(reader, interval, true, function thistype.periodic)
set isrepeating = true
endif
endmethod
method isPlaying takes player for returns boolean
return playing[GetPlayerId(for)]
endmethod
endstruct
endlibrary
---------------------------------
Tutorial on how to import tabs to this system (for Guitar Pro users):
Sorry that I could not provide you with screenshots of the english version of Guitar Pro. But I think the screens might still be helpful.
(1) First, open your midi or .gp file with Guitar Pro. You can download a free trial version of Guitar Pro 6 here:
http://www.guitar-pro.com/en/index.php?pg=download
(2) Select the track you want to extract.
(3) In Guitar Pro 5, click on "Tools", then "Fill up bars with breaks".
(4) Click on your track, check the tuning information for the octave numbers.
(5) Go to "Data" and "Export" and select "Export as Ascii"
(6) A window opens. Set the number of columns to 999 --> 1.
Check your guitar tab for triplet (|-3-| or 3) and accent (>) information. --> 2
If your tab contains those extra lines, make sure to copy them.
(7) Copy and paste everything into a text converted trigger in your WC3 trigger editor.
(8) Create Instruments and import sounds by using the provided struct methods (check example), then create your Track, add your instrument to it. --> 1
Add extra sounds to your instruments if chord notes are needed. --> 2
In case your tab provides triplet information, make sure to copy and paste them too. --> 3
Make sure everything is on the same line: The length information, the triplet information, the last digit of your note value. --> 4
Make sure to push a line of spacebars to other track parts even if there are no triplets on this part. If you use triplets just for one part, you need to put it over every part of this track aswell. --> 5
Adjust the tuning information of the first part by adding the octave number (see step 4). Make sure to add a spacebar to your length, accent and triplet line for this part, to make up for the extra character. --> 6
(9) Add the track to your Song. Repeat this for all tracks.
(10) Use the play method to play your song. Enjoy.
Attachments
Last edited: